Compare commits
60 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
39b19444fe | ||
|
|
644d5a5462 | ||
|
|
8e18451e3f | ||
|
|
3dbdd01f97 | ||
|
|
a89079c005 | ||
|
|
a8c0926b4f | ||
|
|
dd2959a31b | ||
|
|
51099f42c3 | ||
|
|
63f170cc7a | ||
|
|
3c1489e588 | ||
|
|
e4f1e03f62 | ||
|
|
83d48ec990 | ||
|
|
b20d2b2684 | ||
|
|
2b918c70ae | ||
|
|
1100c133ba | ||
|
|
88899f0e89 | ||
|
|
59dc0059bc | ||
|
|
986fb304c0 | ||
|
|
d6435d2885 | ||
|
|
affb456499 | ||
|
|
705ed0a0ac | ||
|
|
dfffe5b508 | ||
|
|
fca102edba | ||
|
|
554b6345a2 | ||
|
|
aa954dc84c | ||
|
|
b5506a1368 | ||
|
|
0b55f94828 | ||
|
|
a67052f48c | ||
|
|
6eff6a9329 | ||
|
|
69d32d4511 | ||
|
|
d7a613b710 | ||
|
|
669c019287 | ||
|
|
fcc4901a10 | ||
|
|
4359503484 | ||
|
|
b13f93a2d3 | ||
|
|
8405e0fad6 | ||
|
|
aceb3f1826 | ||
|
|
a206675f3e | ||
|
|
f4253d74ae | ||
|
|
aaea15e516 | ||
|
|
83d1f80959 | ||
|
|
a33cff8f13 | ||
|
|
8679759f60 | ||
|
|
53deaee3d7 | ||
|
|
5a14a58fe4 | ||
|
|
fb1fbf8f95 | ||
|
|
cfbf779f9b | ||
|
|
d576b6c6c1 | ||
|
|
514eb71482 | ||
|
|
43ed904db1 | ||
|
|
0a440ca629 | ||
|
|
eff1dbf95b | ||
|
|
9a32a94806 | ||
|
|
2534098509 | ||
|
|
9497365758 | ||
|
|
101c44c9c0 | ||
|
|
ffd745c004 | ||
|
|
5fea4eaef8 | ||
|
|
1f610043cf | ||
|
|
3f8de02683 |
67
.github/workflows/codeql-analysis.yml
vendored
Normal file
67
.github/workflows/codeql-analysis.yml
vendored
Normal file
@@ -0,0 +1,67 @@
|
||||
# For most projects, this workflow file will not need changing; you simply need
|
||||
# to commit it to your repository.
|
||||
#
|
||||
# You may wish to alter this file to override the set of languages analyzed,
|
||||
# or to provide custom queries or build logic.
|
||||
#
|
||||
# ******** NOTE ********
|
||||
# We have attempted to detect the languages in your repository. Please check
|
||||
# the `language` matrix defined below to confirm you have the correct set of
|
||||
# supported CodeQL languages.
|
||||
#
|
||||
name: "CodeQL"
|
||||
|
||||
on:
|
||||
push:
|
||||
branches: [ master ]
|
||||
pull_request:
|
||||
# The branches below must be a subset of the branches above
|
||||
branches: [ master ]
|
||||
schedule:
|
||||
- cron: '32 20 * * 0'
|
||||
|
||||
jobs:
|
||||
analyze:
|
||||
name: Analyze
|
||||
runs-on: ubuntu-latest
|
||||
|
||||
strategy:
|
||||
fail-fast: false
|
||||
matrix:
|
||||
language: [ 'go' ]
|
||||
# CodeQL supports [ 'cpp', 'csharp', 'go', 'java', 'javascript', 'python' ]
|
||||
# Learn more:
|
||||
# https://docs.github.com/en/free-pro-team@latest/github/finding-security-vulnerabilities-and-errors-in-your-code/configuring-code-scanning#changing-the-languages-that-are-analyzed
|
||||
|
||||
steps:
|
||||
- name: Checkout repository
|
||||
uses: actions/checkout@v2
|
||||
|
||||
# Initializes the CodeQL tools for scanning.
|
||||
- name: Initialize CodeQL
|
||||
uses: github/codeql-action/init@v1
|
||||
with:
|
||||
languages: ${{ matrix.language }}
|
||||
# If you wish to specify custom queries, you can do so here or in a config file.
|
||||
# By default, queries listed here will override any specified in a config file.
|
||||
# Prefix the list here with "+" to use these queries and those in the config file.
|
||||
# queries: ./path/to/local/query, your-org/your-repo/queries@main
|
||||
|
||||
# Autobuild attempts to build any compiled languages (C/C++, C#, or Java).
|
||||
# If this step fails, then you should remove it and run the build manually (see below)
|
||||
- name: Autobuild
|
||||
uses: github/codeql-action/autobuild@v1
|
||||
|
||||
# ℹ️ Command-line programs to run using the OS shell.
|
||||
# 📚 https://git.io/JvXDl
|
||||
|
||||
# ✏️ If the Autobuild fails above, remove it and uncomment the following three lines
|
||||
# and modify them (or add more) to build your code if your project
|
||||
# uses a compiled language
|
||||
|
||||
#- run: |
|
||||
# make bootstrap
|
||||
# make release
|
||||
|
||||
- name: Perform CodeQL Analysis
|
||||
uses: github/codeql-action/analyze@v1
|
||||
2
.github/workflows/goreleaser.yml
vendored
2
.github/workflows/goreleaser.yml
vendored
@@ -19,7 +19,7 @@ jobs:
|
||||
name: Set up Go
|
||||
uses: actions/setup-go@v2
|
||||
with:
|
||||
go-version: 1.14
|
||||
go-version: 1.15
|
||||
-
|
||||
name: Run GoReleaser
|
||||
uses: goreleaser/goreleaser-action@v2
|
||||
|
||||
2
.github/workflows/test.yml
vendored
2
.github/workflows/test.yml
vendored
@@ -11,7 +11,7 @@ jobs:
|
||||
- name: Set up Go 1.x
|
||||
uses: actions/setup-go@v2
|
||||
with:
|
||||
go-version: 1.14.x
|
||||
go-version: 1.15.x
|
||||
id: go
|
||||
|
||||
- name: Check out code into the Go module directory
|
||||
|
||||
2
.github/workflows/tidy.yml
vendored
2
.github/workflows/tidy.yml
vendored
@@ -19,4 +19,4 @@ jobs:
|
||||
github_token: ${{ secrets.GITHUB_TOKEN }}
|
||||
git_user_name: kotakanbe
|
||||
git_user_email: kotakanbe@gmail.com
|
||||
go_version: 1.14.x
|
||||
go_version: 1.15.6
|
||||
|
||||
1
.gitignore
vendored
1
.gitignore
vendored
@@ -1,4 +1,3 @@
|
||||
vuls
|
||||
.vscode
|
||||
*.txt
|
||||
*.json
|
||||
|
||||
@@ -11,27 +11,60 @@ builds:
|
||||
- linux
|
||||
goarch:
|
||||
- amd64
|
||||
main: .
|
||||
main: ./cmd/vuls/main.go
|
||||
flags:
|
||||
- -a
|
||||
ldflags: -s -w -X main.version={{.Version}} -X main.revision={{.Commit}}
|
||||
- -a
|
||||
ldflags:
|
||||
- -s -w -X github.com/future-architect/vuls/config.Version={{.Version}} -X github.com/future-architect/vuls/config.Revision={{.Commit}}-{{ .CommitDate }}
|
||||
binary: vuls
|
||||
|
||||
- id: trivy-to-vuls
|
||||
- id: vuls-scanner
|
||||
env:
|
||||
- CGO_ENABLED=0
|
||||
goos:
|
||||
- linux
|
||||
goarch:
|
||||
- 386
|
||||
- amd64
|
||||
- arm
|
||||
- arm64
|
||||
main: ./cmd/scanner/main.go
|
||||
flags:
|
||||
- -a
|
||||
- -tags=scanner
|
||||
ldflags:
|
||||
- -s -w -X github.com/future-architect/vuls/config.Version={{.Version}} -X github.com/future-architect/vuls/config.Revision={{.Commit}}-{{ .CommitDate }}
|
||||
binary: vuls-scanner
|
||||
|
||||
- id: trivy-to-vuls
|
||||
env:
|
||||
- CGO_ENABLED=0
|
||||
goos:
|
||||
- linux
|
||||
goarch:
|
||||
- 386
|
||||
- amd64
|
||||
- arm
|
||||
- arm64
|
||||
main: ./contrib/trivy/cmd/main.go
|
||||
binary: trivy-to-vuls
|
||||
|
||||
- id: future-vuls
|
||||
env:
|
||||
- CGO_ENABLED=0
|
||||
goos:
|
||||
- linux
|
||||
goarch:
|
||||
- 386
|
||||
- amd64
|
||||
- arm
|
||||
- arm64
|
||||
flags:
|
||||
- -a
|
||||
- -tags=scanner
|
||||
main: ./contrib/future-vuls/cmd/main.go
|
||||
binary: future-vuls
|
||||
|
||||
archives:
|
||||
|
||||
- id: vuls
|
||||
@@ -45,6 +78,17 @@ archives:
|
||||
- README*
|
||||
- CHANGELOG.md
|
||||
|
||||
- id: vuls-scanner
|
||||
name_template: '{{ .Binary }}_{{.Version}}_{{ .Os }}_{{ .Arch }}{{ if .Arm }}v{{ .Arm }}{{ end }}'
|
||||
builds:
|
||||
- vuls-scanner
|
||||
format: tar.gz
|
||||
files:
|
||||
- LICENSE
|
||||
- NOTICE
|
||||
- README*
|
||||
- CHANGELOG.md
|
||||
|
||||
- id: trivy-to-vuls
|
||||
name_template: '{{ .Binary }}_{{.Version}}_{{ .Os }}_{{ .Arch }}{{ if .Arm }}v{{ .Arm }}{{ end }}'
|
||||
builds:
|
||||
@@ -55,10 +99,10 @@ archives:
|
||||
- NOTICE
|
||||
- README*
|
||||
- CHANGELOG.md
|
||||
|
||||
- id: future-vuls
|
||||
name_template: '{{ .Binary }}_{{.Version}}_{{ .Os }}_{{ .Arch }}{{ if .Arm }}v{{ .Arm }}{{ end }}'
|
||||
builds:
|
||||
|
||||
- future-vuls
|
||||
format: tar.gz
|
||||
files:
|
||||
|
||||
19
GNUmakefile
19
GNUmakefile
@@ -20,19 +20,26 @@ BUILDTIME := $(shell date "+%Y%m%d_%H%M%S")
|
||||
LDFLAGS := -X 'github.com/future-architect/vuls/config.Version=$(VERSION)' \
|
||||
-X 'github.com/future-architect/vuls/config.Revision=build-$(BUILDTIME)_$(REVISION)'
|
||||
GO := GO111MODULE=on go
|
||||
CGO_UNABLED := CGO_ENABLED=0 go
|
||||
GO_OFF := GO111MODULE=off go
|
||||
|
||||
|
||||
all: build
|
||||
|
||||
build: main.go pretest fmt
|
||||
$(GO) build -a -ldflags "$(LDFLAGS)" -o vuls $<
|
||||
build: ./cmd/vuls/main.go pretest fmt
|
||||
$(GO) build -a -ldflags "$(LDFLAGS)" -o vuls ./cmd/vuls
|
||||
|
||||
b: main.go pretest fmt
|
||||
$(GO) build -ldflags "$(LDFLAGS)" -o vuls $<
|
||||
b: ./cmd/vuls/main.go
|
||||
$(GO) build -a -ldflags "$(LDFLAGS)" -o vuls ./cmd/vuls
|
||||
|
||||
install: main.go pretest
|
||||
$(GO) install -ldflags "$(LDFLAGS)"
|
||||
install: ./cmd/vuls/main.go pretest fmt
|
||||
$(GO) install -ldflags "$(LDFLAGS)" ./cmd/vuls
|
||||
|
||||
build-scanner: ./cmd/scanner/main.go pretest fmt
|
||||
$(CGO_UNABLED) build -tags=scanner -a -ldflags "$(LDFLAGS)" -o vuls ./cmd/scanner
|
||||
|
||||
install-scanner: ./cmd/scanner/main.go pretest fmt
|
||||
$(CGO_UNABLED) install -tags=scanner -ldflags "$(LDFLAGS)" ./cmd/scanner
|
||||
|
||||
lint:
|
||||
$(GO_OFF) get -u golang.org/x/lint/golint
|
||||
|
||||
@@ -92,7 +92,7 @@ Vuls is a tool created to solve the problems listed above. It has the following
|
||||
- [RustSec Advisory Database](https://github.com/RustSec/advisory-db)
|
||||
|
||||
- WordPress
|
||||
- [WPVulnDB](https://wpvulndb.com/api)
|
||||
- [wpscan](https://wpscan.com/api)
|
||||
|
||||
### Scan mode
|
||||
|
||||
@@ -177,6 +177,10 @@ For more information such as Installation, Tutorial, Usage, visit [vuls.io](http
|
||||
|
||||
kotakanbe ([@kotakanbe](https://twitter.com/kotakanbe)) created vuls and [these fine people](https://github.com/future-architect/vuls/graphs/contributors) have contributed.
|
||||
|
||||
## Contribute
|
||||
|
||||
see [vulsdoc](https://vuls.io/docs/en/how-to-contribute.html)
|
||||
|
||||
----
|
||||
|
||||
## Stargazers over time
|
||||
|
||||
36
cmd/scanner/main.go
Normal file
36
cmd/scanner/main.go
Normal file
@@ -0,0 +1,36 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"flag"
|
||||
"fmt"
|
||||
"os"
|
||||
|
||||
"context"
|
||||
|
||||
"github.com/future-architect/vuls/config"
|
||||
commands "github.com/future-architect/vuls/subcmds"
|
||||
"github.com/google/subcommands"
|
||||
)
|
||||
|
||||
func main() {
|
||||
subcommands.Register(subcommands.HelpCommand(), "")
|
||||
subcommands.Register(subcommands.FlagsCommand(), "")
|
||||
subcommands.Register(subcommands.CommandsCommand(), "")
|
||||
subcommands.Register(&commands.DiscoverCmd{}, "discover")
|
||||
subcommands.Register(&commands.ScanCmd{}, "scan")
|
||||
subcommands.Register(&commands.HistoryCmd{}, "history")
|
||||
subcommands.Register(&commands.ConfigtestCmd{}, "configtest")
|
||||
subcommands.Register(&commands.SaaSCmd{}, "saas")
|
||||
|
||||
var v = flag.Bool("v", false, "Show version")
|
||||
|
||||
flag.Parse()
|
||||
|
||||
if *v {
|
||||
fmt.Printf("vuls %s %s\n", config.Version, config.Revision)
|
||||
os.Exit(int(subcommands.ExitSuccess))
|
||||
}
|
||||
|
||||
ctx := context.Background()
|
||||
os.Exit(int(subcommands.Execute(ctx)))
|
||||
}
|
||||
@@ -7,8 +7,8 @@ import (
|
||||
|
||||
"context"
|
||||
|
||||
"github.com/future-architect/vuls/commands"
|
||||
"github.com/future-architect/vuls/config"
|
||||
commands "github.com/future-architect/vuls/subcmds"
|
||||
"github.com/google/subcommands"
|
||||
)
|
||||
|
||||
@@ -1,241 +0,0 @@
|
||||
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/msf"
|
||||
"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"
|
||||
)
|
||||
|
||||
// ServerCmd is subcommand for server
|
||||
type ServerCmd struct {
|
||||
configPath string
|
||||
listen string
|
||||
cveDict c.GoCveDictConf
|
||||
ovalDict c.GovalDictConf
|
||||
gostConf c.GostConf
|
||||
exploitConf c.ExploitConf
|
||||
metasploitConf c.MetasploitConf
|
||||
}
|
||||
|
||||
// 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]
|
||||
[-msfdb-type=sqlite3|mysql|redis|http]
|
||||
[-msfdb-sqlite3-path=/path/to/msfdb.sqlite3]
|
||||
[-msfdb-url=http://127.0.0.1:1327 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()
|
||||
f.StringVar(&p.configPath, "config", "", "/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")
|
||||
|
||||
f.StringVar(&p.metasploitConf.Type, "msfdb-type", "",
|
||||
"DB type of msf (sqlite3, mysql, postgres, redis or http)")
|
||||
f.StringVar(&p.metasploitConf.SQLite3Path, "msfdb-sqlite3-path", "", "/path/to/sqlite3")
|
||||
f.StringVar(&p.metasploitConf.URL, "msfdb-url", "",
|
||||
"http://metasploit.com:1327 or DB connection string")
|
||||
}
|
||||
|
||||
// Execute execute
|
||||
func (p *ServerCmd) Execute(_ context.Context, f *flag.FlagSet, _ ...interface{}) subcommands.ExitStatus {
|
||||
util.Log = util.NewCustomLogger(c.ServerInfo{})
|
||||
if p.configPath != "" {
|
||||
if err := c.Load(p.configPath, ""); err != nil {
|
||||
util.Log.Errorf("Error loading %s. err: %+v", 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)
|
||||
c.Conf.Metasploit.Overwrite(p.metasploitConf)
|
||||
|
||||
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: %+v", 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: %+v", 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: %+v", err)
|
||||
util.Log.Errorf("Run go-exploitdb as server mode before reporting")
|
||||
return subcommands.ExitFailure
|
||||
}
|
||||
}
|
||||
|
||||
if c.Conf.Metasploit.URL != "" {
|
||||
err := msf.CheckHTTPHealth()
|
||||
if err != nil {
|
||||
util.Log.Errorf("metasploit HTTP server is not running. err: %+v", err)
|
||||
util.Log.Errorf("Run go-msfdb 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,
|
||||
MetasploitCnf: c.Conf.Metasploit,
|
||||
DebugSQL: c.Conf.DebugSQL,
|
||||
})
|
||||
if locked {
|
||||
util.Log.Errorf("SQLite3 is locked. Close other DB connections and try again: %+v", err)
|
||||
return subcommands.ExitFailure
|
||||
}
|
||||
|
||||
if err != nil {
|
||||
util.Log.Errorf("Failed to init DB Clients. err: %+v", 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. err: %+v", err)
|
||||
return subcommands.ExitFailure
|
||||
}
|
||||
return subcommands.ExitSuccess
|
||||
}
|
||||
265
commands/tui.go
265
commands/tui.go
@@ -1,265 +0,0 @@
|
||||
package commands
|
||||
|
||||
import (
|
||||
"context"
|
||||
"flag"
|
||||
"os"
|
||||
"path/filepath"
|
||||
|
||||
"github.com/aquasecurity/trivy/pkg/utils"
|
||||
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/msf"
|
||||
"github.com/future-architect/vuls/oval"
|
||||
"github.com/future-architect/vuls/report"
|
||||
"github.com/future-architect/vuls/util"
|
||||
"github.com/google/subcommands"
|
||||
)
|
||||
|
||||
// TuiCmd is Subcommand of host discovery mode
|
||||
type TuiCmd struct {
|
||||
configPath string
|
||||
cveDict c.GoCveDictConf
|
||||
ovalDict c.GovalDictConf
|
||||
gostConf c.GostConf
|
||||
exploitConf c.ExploitConf
|
||||
metasploitConf c.MetasploitConf
|
||||
}
|
||||
|
||||
// Name return subcommand name
|
||||
func (*TuiCmd) Name() string { return "tui" }
|
||||
|
||||
// Synopsis return synopsis
|
||||
func (*TuiCmd) Synopsis() string { return "Run Tui view to analyze vulnerabilities" }
|
||||
|
||||
// Usage return usage
|
||||
func (*TuiCmd) Usage() string {
|
||||
return `tui:
|
||||
tui
|
||||
[-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]
|
||||
[-quiet]
|
||||
[-no-progress]
|
||||
[-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]
|
||||
[-msfdb-type=sqlite3|mysql|redis|http]
|
||||
[-msfdb-sqlite3-path=/path/to/msfdb.sqlite3]
|
||||
[-msfdb-url=http://127.0.0.1:1327 or DB connection string]
|
||||
[-trivy-cachedb-dir=/path/to/dir]
|
||||
|
||||
`
|
||||
}
|
||||
|
||||
// SetFlags set flag
|
||||
func (p *TuiCmd) SetFlags(f *flag.FlagSet) {
|
||||
// f.StringVar(&p.lang, "lang", "en", "[en|ja]")
|
||||
f.BoolVar(&c.Conf.DebugSQL, "debug-sql", false, "debug SQL")
|
||||
f.BoolVar(&c.Conf.Debug, "debug", false, "debug mode")
|
||||
f.BoolVar(&c.Conf.Quiet, "quiet", false, "Quiet mode. No output on stdout")
|
||||
f.BoolVar(&c.Conf.NoProgress, "no-progress", false, "Suppress progress bar")
|
||||
|
||||
defaultLogDir := util.GetDefaultLogDir()
|
||||
f.StringVar(&c.Conf.LogDir, "log-dir", defaultLogDir, "/path/to/log")
|
||||
|
||||
wd, _ := os.Getwd()
|
||||
defaultResultsDir := filepath.Join(wd, "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(&c.Conf.RefreshCve, "refresh-cve", false,
|
||||
"Refresh CVE information in JSON file under results dir")
|
||||
|
||||
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(&c.Conf.Diff, "diff", false,
|
||||
"Difference between previous result and current result ")
|
||||
|
||||
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.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")
|
||||
|
||||
f.StringVar(&p.metasploitConf.Type, "msfdb-type", "",
|
||||
"DB type of msf (sqlite3, mysql, postgres, redis or http)")
|
||||
f.StringVar(&p.metasploitConf.SQLite3Path, "msfdb-sqlite3-path", "", "/path/to/sqlite3")
|
||||
f.StringVar(&p.metasploitConf.URL, "msfdb-url", "",
|
||||
"http://metasploit.com:1327 or DB connection string")
|
||||
|
||||
f.StringVar(&c.Conf.TrivyCacheDBDir, "trivy-cachedb-dir",
|
||||
utils.DefaultCacheDir(), "/path/to/dir")
|
||||
}
|
||||
|
||||
// Execute execute
|
||||
func (p *TuiCmd) Execute(_ context.Context, f *flag.FlagSet, _ ...interface{}) subcommands.ExitStatus {
|
||||
util.Log = util.NewCustomLogger(c.ServerInfo{})
|
||||
if err := c.Load(p.configPath, ""); err != nil {
|
||||
util.Log.Errorf("Error loading %s, err: %+v", p.configPath, err)
|
||||
return subcommands.ExitUsageError
|
||||
}
|
||||
|
||||
c.Conf.Lang = "en"
|
||||
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.Metasploit.Overwrite(p.metasploitConf)
|
||||
|
||||
var dir string
|
||||
var err error
|
||||
if c.Conf.Diff {
|
||||
dir, err = report.JSONDir([]string{})
|
||||
} else {
|
||||
dir, err = report.JSONDir(f.Args())
|
||||
}
|
||||
if err != nil {
|
||||
util.Log.Errorf("Failed to read from JSON. err: %+v", 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)
|
||||
return subcommands.ExitFailure
|
||||
}
|
||||
util.Log.Infof("Loaded: %s", dir)
|
||||
|
||||
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: %+v", 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: %+v", 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: %+v", 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: %+v", err)
|
||||
util.Log.Errorf("Run go-exploitdb as server mode before reporting")
|
||||
return subcommands.ExitFailure
|
||||
}
|
||||
}
|
||||
|
||||
if c.Conf.Metasploit.URL != "" {
|
||||
err := msf.CheckHTTPHealth()
|
||||
if err != nil {
|
||||
util.Log.Errorf("metasploit HTTP server is not running. err: %+v", err)
|
||||
util.Log.Errorf("Run go-msfdb 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,
|
||||
MetasploitCnf: c.Conf.Metasploit,
|
||||
DebugSQL: c.Conf.DebugSQL,
|
||||
})
|
||||
if locked {
|
||||
util.Log.Errorf("SQLite3 is locked. Close other DB connections and try again: %+v", err)
|
||||
return subcommands.ExitFailure
|
||||
}
|
||||
|
||||
if err != nil {
|
||||
util.Log.Errorf("Failed to init DB Clients. err: %+v", 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 _, r := range res {
|
||||
if len(r.Warnings) != 0 {
|
||||
util.Log.Warnf("Warning: Some warnings occurred while scanning on %s: %s",
|
||||
r.FormatServerName(), r.Warnings)
|
||||
}
|
||||
}
|
||||
|
||||
return report.RunTui(res)
|
||||
}
|
||||
32
config/chatworkconf.go
Normal file
32
config/chatworkconf.go
Normal file
@@ -0,0 +1,32 @@
|
||||
package config
|
||||
|
||||
import (
|
||||
"github.com/asaskevich/govalidator"
|
||||
"golang.org/x/xerrors"
|
||||
)
|
||||
|
||||
// ChatWorkConf is ChatWork config
|
||||
type ChatWorkConf struct {
|
||||
APIToken string `json:"-"`
|
||||
Room string `json:"-"`
|
||||
}
|
||||
|
||||
// Validate validates configuration
|
||||
func (c *ChatWorkConf) Validate() (errs []error) {
|
||||
if !Conf.ToChatWork {
|
||||
return
|
||||
}
|
||||
if len(c.Room) == 0 {
|
||||
errs = append(errs, xerrors.New("chatWorkConf.room must not be empty"))
|
||||
}
|
||||
|
||||
if len(c.APIToken) == 0 {
|
||||
errs = append(errs, xerrors.New("chatWorkConf.ApiToken must not be empty"))
|
||||
}
|
||||
|
||||
_, err := govalidator.ValidateStruct(c)
|
||||
if err != nil {
|
||||
errs = append(errs, err)
|
||||
}
|
||||
return
|
||||
}
|
||||
997
config/config.go
997
config/config.go
File diff suppressed because it is too large
Load Diff
73
config/exploitconf.go
Normal file
73
config/exploitconf.go
Normal file
@@ -0,0 +1,73 @@
|
||||
package config
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"os"
|
||||
"path/filepath"
|
||||
|
||||
"github.com/parnurzeal/gorequest"
|
||||
"golang.org/x/xerrors"
|
||||
)
|
||||
|
||||
// ExploitConf is exploit config
|
||||
type ExploitConf struct {
|
||||
// DB type for exploit dictionary (sqlite3, mysql, postgres or redis)
|
||||
Type string
|
||||
|
||||
// http://exploit-dictionary.com:1324 or DB connection string
|
||||
URL string `json:"-"`
|
||||
|
||||
// /path/to/exploit.sqlite3
|
||||
SQLite3Path string `json:"-"`
|
||||
}
|
||||
|
||||
func (cnf *ExploitConf) setDefault() {
|
||||
if cnf.Type == "" {
|
||||
cnf.Type = "sqlite3"
|
||||
}
|
||||
if cnf.URL == "" && cnf.SQLite3Path == "" {
|
||||
wd, _ := os.Getwd()
|
||||
cnf.SQLite3Path = filepath.Join(wd, "go-exploitdb.sqlite3")
|
||||
}
|
||||
}
|
||||
|
||||
const exploitDBType = "EXPLOITDB_TYPE"
|
||||
const exploitDBURL = "EXPLOITDB_URL"
|
||||
const exploitDBPATH = "EXPLOITDB_SQLITE3_PATH"
|
||||
|
||||
// Init set options with the following priority.
|
||||
// 1. Environment variable
|
||||
// 2. config.toml
|
||||
func (cnf *ExploitConf) Init() {
|
||||
if os.Getenv(exploitDBType) != "" {
|
||||
cnf.Type = os.Getenv(exploitDBType)
|
||||
}
|
||||
if os.Getenv(exploitDBURL) != "" {
|
||||
cnf.URL = os.Getenv(exploitDBURL)
|
||||
}
|
||||
if os.Getenv(exploitDBPATH) != "" {
|
||||
cnf.SQLite3Path = os.Getenv(exploitDBPATH)
|
||||
}
|
||||
cnf.setDefault()
|
||||
}
|
||||
|
||||
// IsFetchViaHTTP returns wether fetch via http
|
||||
func (cnf *ExploitConf) IsFetchViaHTTP() bool {
|
||||
return Conf.Exploit.Type == "http"
|
||||
}
|
||||
|
||||
// CheckHTTPHealth do health check
|
||||
func (cnf *ExploitConf) CheckHTTPHealth() error {
|
||||
if !cnf.IsFetchViaHTTP() {
|
||||
return nil
|
||||
}
|
||||
|
||||
url := fmt.Sprintf("%s/health", cnf.URL)
|
||||
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 xerrors.Errorf("Failed to connect to exploit server. url: %s, errs: %s", url, errs)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
73
config/gocvedictconf.go
Normal file
73
config/gocvedictconf.go
Normal file
@@ -0,0 +1,73 @@
|
||||
package config
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"os"
|
||||
"path/filepath"
|
||||
|
||||
"github.com/parnurzeal/gorequest"
|
||||
"golang.org/x/xerrors"
|
||||
)
|
||||
|
||||
// GoCveDictConf is go-cve-dictionary config
|
||||
type GoCveDictConf struct {
|
||||
// DB type of CVE dictionary (sqlite3, mysql, postgres or redis)
|
||||
Type string
|
||||
|
||||
// http://cve-dictionary.com:1323 or DB connection string
|
||||
URL string `json:"-"`
|
||||
|
||||
// /path/to/cve.sqlite3
|
||||
SQLite3Path string `json:"-"`
|
||||
}
|
||||
|
||||
func (cnf *GoCveDictConf) setDefault() {
|
||||
if cnf.Type == "" {
|
||||
cnf.Type = "sqlite3"
|
||||
}
|
||||
if cnf.URL == "" && cnf.SQLite3Path == "" {
|
||||
wd, _ := os.Getwd()
|
||||
cnf.SQLite3Path = filepath.Join(wd, "cve.sqlite3")
|
||||
}
|
||||
}
|
||||
|
||||
const cveDBType = "CVEDB_TYPE"
|
||||
const cveDBURL = "CVEDB_URL"
|
||||
const cveDBPATH = "CVEDB_SQLITE3_PATH"
|
||||
|
||||
// Init set options with the following priority.
|
||||
// 1. Environment variable
|
||||
// 2. config.toml
|
||||
func (cnf *GoCveDictConf) Init() {
|
||||
if os.Getenv(cveDBType) != "" {
|
||||
cnf.Type = os.Getenv(cveDBType)
|
||||
}
|
||||
if os.Getenv(cveDBURL) != "" {
|
||||
cnf.URL = os.Getenv(cveDBURL)
|
||||
}
|
||||
if os.Getenv(cveDBPATH) != "" {
|
||||
cnf.SQLite3Path = os.Getenv(cveDBPATH)
|
||||
}
|
||||
cnf.setDefault()
|
||||
}
|
||||
|
||||
// IsFetchViaHTTP returns wether fetch via http
|
||||
func (cnf *GoCveDictConf) IsFetchViaHTTP() bool {
|
||||
return Conf.CveDict.Type == "http"
|
||||
}
|
||||
|
||||
// CheckHTTPHealth checks http server status
|
||||
func (cnf *GoCveDictConf) CheckHTTPHealth() error {
|
||||
if !cnf.IsFetchViaHTTP() {
|
||||
return nil
|
||||
}
|
||||
|
||||
url := fmt.Sprintf("%s/health", cnf.URL)
|
||||
resp, _, errs := gorequest.New().SetDebug(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 xerrors.Errorf("Failed to request to CVE server. url: %s, errs: %s",
|
||||
url, errs)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
73
config/gostconf.go
Normal file
73
config/gostconf.go
Normal file
@@ -0,0 +1,73 @@
|
||||
package config
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"os"
|
||||
"path/filepath"
|
||||
|
||||
"github.com/parnurzeal/gorequest"
|
||||
"golang.org/x/xerrors"
|
||||
)
|
||||
|
||||
// GostConf is gost config
|
||||
type GostConf struct {
|
||||
// DB type for gost dictionary (sqlite3, mysql, postgres or redis)
|
||||
Type string
|
||||
|
||||
// http://gost-dictionary.com:1324 or DB connection string
|
||||
URL string `json:"-"`
|
||||
|
||||
// /path/to/gost.sqlite3
|
||||
SQLite3Path string `json:"-"`
|
||||
}
|
||||
|
||||
func (cnf *GostConf) setDefault() {
|
||||
if cnf.Type == "" {
|
||||
cnf.Type = "sqlite3"
|
||||
}
|
||||
if cnf.URL == "" && cnf.SQLite3Path == "" {
|
||||
wd, _ := os.Getwd()
|
||||
cnf.SQLite3Path = filepath.Join(wd, "gost.sqlite3")
|
||||
}
|
||||
}
|
||||
|
||||
const gostDBType = "GOSTDB_TYPE"
|
||||
const gostDBURL = "GOSTDB_URL"
|
||||
const gostDBPATH = "GOSTDB_SQLITE3_PATH"
|
||||
|
||||
// Init set options with the following priority.
|
||||
// 1. Environment variable
|
||||
// 2. config.toml
|
||||
func (cnf *GostConf) Init() {
|
||||
if os.Getenv(gostDBType) != "" {
|
||||
cnf.Type = os.Getenv(gostDBType)
|
||||
}
|
||||
if os.Getenv(gostDBURL) != "" {
|
||||
cnf.URL = os.Getenv(gostDBURL)
|
||||
}
|
||||
if os.Getenv(gostDBPATH) != "" {
|
||||
cnf.SQLite3Path = os.Getenv(gostDBPATH)
|
||||
}
|
||||
cnf.setDefault()
|
||||
}
|
||||
|
||||
// IsFetchViaHTTP returns wether fetch via http
|
||||
func (cnf *GostConf) IsFetchViaHTTP() bool {
|
||||
return Conf.Gost.Type == "http"
|
||||
}
|
||||
|
||||
// CheckHTTPHealth do health check
|
||||
func (cnf *GostConf) CheckHTTPHealth() error {
|
||||
if !cnf.IsFetchViaHTTP() {
|
||||
return nil
|
||||
}
|
||||
|
||||
url := fmt.Sprintf("%s/health", cnf.URL)
|
||||
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 xerrors.Errorf("Failed to connect to gost server. url: %s, errs: %s", url, errs)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
75
config/govaldictconf.go
Normal file
75
config/govaldictconf.go
Normal file
@@ -0,0 +1,75 @@
|
||||
package config
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"os"
|
||||
"path/filepath"
|
||||
|
||||
"github.com/parnurzeal/gorequest"
|
||||
"golang.org/x/xerrors"
|
||||
)
|
||||
|
||||
// GovalDictConf is goval-dictionary config
|
||||
type GovalDictConf struct {
|
||||
|
||||
// DB type of OVAL dictionary (sqlite3, mysql, postgres or redis)
|
||||
Type string
|
||||
|
||||
// http://goval-dictionary.com:1324 or DB connection string
|
||||
URL string `json:"-"`
|
||||
|
||||
// /path/to/oval.sqlite3
|
||||
SQLite3Path string `json:"-"`
|
||||
}
|
||||
|
||||
func (cnf *GovalDictConf) setDefault() {
|
||||
if cnf.Type == "" {
|
||||
cnf.Type = "sqlite3"
|
||||
}
|
||||
if cnf.URL == "" && cnf.SQLite3Path == "" {
|
||||
wd, _ := os.Getwd()
|
||||
cnf.SQLite3Path = filepath.Join(wd, "oval.sqlite3")
|
||||
}
|
||||
}
|
||||
|
||||
const govalType = "OVALDB_TYPE"
|
||||
const govalURL = "OVALDB_URL"
|
||||
const govalPATH = "OVALDB_SQLITE3_PATH"
|
||||
|
||||
// Init set options with the following priority.
|
||||
// 1. Environment variable
|
||||
// 2. config.toml
|
||||
func (cnf *GovalDictConf) Init() {
|
||||
if os.Getenv(govalType) != "" {
|
||||
cnf.Type = os.Getenv(govalType)
|
||||
}
|
||||
if os.Getenv(govalURL) != "" {
|
||||
cnf.URL = os.Getenv(govalURL)
|
||||
}
|
||||
if os.Getenv(govalPATH) != "" {
|
||||
cnf.SQLite3Path = os.Getenv(govalPATH)
|
||||
}
|
||||
cnf.setDefault()
|
||||
}
|
||||
|
||||
// IsFetchViaHTTP returns wether fetch via http
|
||||
func (cnf *GovalDictConf) IsFetchViaHTTP() bool {
|
||||
return Conf.OvalDict.Type == "http"
|
||||
}
|
||||
|
||||
// CheckHTTPHealth do health check
|
||||
func (cnf *GovalDictConf) CheckHTTPHealth() error {
|
||||
if !cnf.IsFetchViaHTTP() {
|
||||
return nil
|
||||
}
|
||||
|
||||
url := fmt.Sprintf("%s/health", cnf.URL)
|
||||
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 xerrors.Errorf("Failed to request to OVAL server. url: %s, errs: %s",
|
||||
url, errs)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
38
config/httpconf.go
Normal file
38
config/httpconf.go
Normal file
@@ -0,0 +1,38 @@
|
||||
package config
|
||||
|
||||
import (
|
||||
"os"
|
||||
|
||||
"github.com/asaskevich/govalidator"
|
||||
)
|
||||
|
||||
// HTTPConf is HTTP config
|
||||
type HTTPConf struct {
|
||||
URL string `valid:"url" json:"-"`
|
||||
}
|
||||
|
||||
// Validate validates configuration
|
||||
func (c *HTTPConf) Validate() (errs []error) {
|
||||
if !Conf.ToHTTP {
|
||||
return nil
|
||||
}
|
||||
|
||||
if _, err := govalidator.ValidateStruct(c); err != nil {
|
||||
errs = append(errs, err)
|
||||
}
|
||||
return errs
|
||||
}
|
||||
|
||||
const httpKey = "VULS_HTTP_URL"
|
||||
|
||||
// Init set options with the following priority.
|
||||
// 1. Environment variable
|
||||
// 2. config.toml
|
||||
func (c *HTTPConf) Init(toml HTTPConf) {
|
||||
if os.Getenv(httpKey) != "" {
|
||||
c.URL = os.Getenv(httpKey)
|
||||
}
|
||||
if toml.URL != "" {
|
||||
c.URL = toml.URL
|
||||
}
|
||||
}
|
||||
73
config/metasploitconf.go
Normal file
73
config/metasploitconf.go
Normal file
@@ -0,0 +1,73 @@
|
||||
package config
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"os"
|
||||
"path/filepath"
|
||||
|
||||
"github.com/parnurzeal/gorequest"
|
||||
"golang.org/x/xerrors"
|
||||
)
|
||||
|
||||
// MetasploitConf is metasploit config
|
||||
type MetasploitConf struct {
|
||||
// DB type for metasploit dictionary (sqlite3, mysql, postgres or redis)
|
||||
Type string
|
||||
|
||||
// http://metasploit-dictionary.com:1324 or DB connection string
|
||||
URL string `json:"-"`
|
||||
|
||||
// /path/to/metasploit.sqlite3
|
||||
SQLite3Path string `json:"-"`
|
||||
}
|
||||
|
||||
func (cnf *MetasploitConf) setDefault() {
|
||||
if cnf.Type == "" {
|
||||
cnf.Type = "sqlite3"
|
||||
}
|
||||
if cnf.URL == "" && cnf.SQLite3Path == "" {
|
||||
wd, _ := os.Getwd()
|
||||
cnf.SQLite3Path = filepath.Join(wd, "go-msfdb.sqlite3")
|
||||
}
|
||||
}
|
||||
|
||||
const metasploitDBType = "METASPLOITDB_TYPE"
|
||||
const metasploitDBURL = "METASPLOITDB_URL"
|
||||
const metasploitDBPATH = "METASPLOITDB_SQLITE3_PATH"
|
||||
|
||||
// Init set options with the following priority.
|
||||
// 1. Environment variable
|
||||
// 2. config.toml
|
||||
func (cnf *MetasploitConf) Init() {
|
||||
if os.Getenv(metasploitDBType) != "" {
|
||||
cnf.Type = os.Getenv(metasploitDBType)
|
||||
}
|
||||
if os.Getenv(metasploitDBURL) != "" {
|
||||
cnf.URL = os.Getenv(metasploitDBURL)
|
||||
}
|
||||
if os.Getenv(metasploitDBPATH) != "" {
|
||||
cnf.SQLite3Path = os.Getenv(metasploitDBPATH)
|
||||
}
|
||||
cnf.setDefault()
|
||||
}
|
||||
|
||||
// IsFetchViaHTTP returns wether fetch via http
|
||||
func (cnf *MetasploitConf) IsFetchViaHTTP() bool {
|
||||
return Conf.Metasploit.Type == "http"
|
||||
}
|
||||
|
||||
// CheckHTTPHealth do health check
|
||||
func (cnf *MetasploitConf) CheckHTTPHealth() error {
|
||||
if !cnf.IsFetchViaHTTP() {
|
||||
return nil
|
||||
}
|
||||
|
||||
url := fmt.Sprintf("%s/health", cnf.URL)
|
||||
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 xerrors.Errorf("Failed to connect to metasploit server. url: %s, errs: %s", url, errs)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
249
config/os.go
Normal file
249
config/os.go
Normal file
@@ -0,0 +1,249 @@
|
||||
package config
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"strings"
|
||||
"time"
|
||||
)
|
||||
|
||||
const (
|
||||
// RedHat is
|
||||
RedHat = "redhat"
|
||||
|
||||
// Debian is
|
||||
Debian = "debian"
|
||||
|
||||
// Ubuntu is
|
||||
Ubuntu = "ubuntu"
|
||||
|
||||
// CentOS is
|
||||
CentOS = "centos"
|
||||
|
||||
// Fedora is
|
||||
// Fedora = "fedora"
|
||||
|
||||
// Amazon is
|
||||
Amazon = "amazon"
|
||||
|
||||
// Oracle is
|
||||
Oracle = "oracle"
|
||||
|
||||
// FreeBSD is
|
||||
FreeBSD = "freebsd"
|
||||
|
||||
// Raspbian is
|
||||
Raspbian = "raspbian"
|
||||
|
||||
// Windows is
|
||||
Windows = "windows"
|
||||
|
||||
// OpenSUSE is
|
||||
OpenSUSE = "opensuse"
|
||||
|
||||
// OpenSUSELeap is
|
||||
OpenSUSELeap = "opensuse.leap"
|
||||
|
||||
// SUSEEnterpriseServer is
|
||||
SUSEEnterpriseServer = "suse.linux.enterprise.server"
|
||||
|
||||
// SUSEEnterpriseDesktop is
|
||||
SUSEEnterpriseDesktop = "suse.linux.enterprise.desktop"
|
||||
|
||||
// SUSEOpenstackCloud is
|
||||
SUSEOpenstackCloud = "suse.openstack.cloud"
|
||||
|
||||
// Alpine is
|
||||
Alpine = "alpine"
|
||||
|
||||
// ServerTypePseudo is used for ServerInfo.Type, r.Family
|
||||
ServerTypePseudo = "pseudo"
|
||||
)
|
||||
|
||||
// EOL has End-of-Life information
|
||||
type EOL struct {
|
||||
StandardSupportUntil time.Time
|
||||
ExtendedSupportUntil time.Time
|
||||
Ended bool
|
||||
}
|
||||
|
||||
// IsStandardSupportEnded checks now is under standard support
|
||||
func (e EOL) IsStandardSupportEnded(now time.Time) bool {
|
||||
return e.Ended ||
|
||||
!e.ExtendedSupportUntil.IsZero() && e.StandardSupportUntil.IsZero() ||
|
||||
!e.StandardSupportUntil.IsZero() && now.After(e.StandardSupportUntil)
|
||||
}
|
||||
|
||||
// IsExtendedSuppportEnded checks now is under extended support
|
||||
func (e EOL) IsExtendedSuppportEnded(now time.Time) bool {
|
||||
if e.Ended {
|
||||
return true
|
||||
}
|
||||
if e.StandardSupportUntil.IsZero() && e.ExtendedSupportUntil.IsZero() {
|
||||
return false
|
||||
}
|
||||
return !e.ExtendedSupportUntil.IsZero() && now.After(e.ExtendedSupportUntil) ||
|
||||
e.ExtendedSupportUntil.IsZero() && now.After(e.StandardSupportUntil)
|
||||
}
|
||||
|
||||
// GetEOL return EOL information for the OS-release passed by args
|
||||
// https://github.com/aquasecurity/trivy/blob/master/pkg/detector/ospkg/redhat/redhat.go#L20
|
||||
func GetEOL(family, release string) (eol EOL, found bool) {
|
||||
switch family {
|
||||
case Amazon:
|
||||
rel := "2"
|
||||
if isAmazonLinux1(release) {
|
||||
rel = "1"
|
||||
}
|
||||
eol, found = map[string]EOL{
|
||||
"1": {StandardSupportUntil: time.Date(2023, 6, 30, 23, 59, 59, 0, time.UTC)},
|
||||
"2": {},
|
||||
}[rel]
|
||||
case RedHat:
|
||||
// https://access.redhat.com/support/policy/updates/errata
|
||||
eol, found = map[string]EOL{
|
||||
"3": {Ended: true},
|
||||
"4": {Ended: true},
|
||||
"5": {Ended: true},
|
||||
"6": {
|
||||
StandardSupportUntil: time.Date(2020, 11, 30, 23, 59, 59, 0, time.UTC),
|
||||
ExtendedSupportUntil: time.Date(2024, 6, 30, 23, 59, 59, 0, time.UTC),
|
||||
},
|
||||
"7": {
|
||||
StandardSupportUntil: time.Date(2024, 6, 30, 23, 59, 59, 0, time.UTC),
|
||||
},
|
||||
"8": {
|
||||
StandardSupportUntil: time.Date(2029, 5, 31, 23, 59, 59, 0, time.UTC),
|
||||
},
|
||||
}[major(release)]
|
||||
case CentOS:
|
||||
// https://en.wikipedia.org/wiki/CentOS#End-of-support_schedule
|
||||
// TODO Stream
|
||||
eol, found = map[string]EOL{
|
||||
"3": {Ended: true},
|
||||
"4": {Ended: true},
|
||||
"5": {Ended: true},
|
||||
"6": {Ended: true},
|
||||
"7": {StandardSupportUntil: time.Date(2024, 6, 30, 23, 59, 59, 0, time.UTC)},
|
||||
"8": {StandardSupportUntil: time.Date(2021, 12, 31, 23, 59, 59, 0, time.UTC)},
|
||||
}[major(release)]
|
||||
case Oracle:
|
||||
eol, found = map[string]EOL{
|
||||
// Source:
|
||||
// https://www.oracle.com/a/ocom/docs/elsp-lifetime-069338.pdf
|
||||
// https://community.oracle.com/docs/DOC-917964
|
||||
"3": {Ended: true},
|
||||
"4": {Ended: true},
|
||||
"5": {Ended: true},
|
||||
"6": {
|
||||
StandardSupportUntil: time.Date(2021, 3, 1, 23, 59, 59, 0, time.UTC),
|
||||
ExtendedSupportUntil: time.Date(2024, 3, 1, 23, 59, 59, 0, time.UTC),
|
||||
},
|
||||
"7": {
|
||||
StandardSupportUntil: time.Date(2024, 7, 1, 23, 59, 59, 0, time.UTC),
|
||||
},
|
||||
"8": {
|
||||
StandardSupportUntil: time.Date(2029, 7, 1, 23, 59, 59, 0, time.UTC),
|
||||
},
|
||||
}[major(release)]
|
||||
case Debian:
|
||||
eol, found = map[string]EOL{
|
||||
// https://wiki.debian.org/LTS
|
||||
"6": {Ended: true},
|
||||
"7": {Ended: true},
|
||||
"8": {Ended: true},
|
||||
"9": {StandardSupportUntil: time.Date(2022, 6, 30, 23, 59, 59, 0, time.UTC)},
|
||||
"10": {StandardSupportUntil: time.Date(2024, 6, 30, 23, 59, 59, 0, time.UTC)},
|
||||
}[major(release)]
|
||||
case Raspbian:
|
||||
// Not found
|
||||
eol, found = map[string]EOL{}[major(release)]
|
||||
case Ubuntu:
|
||||
// https://wiki.ubuntu.com/Releases
|
||||
eol, found = map[string]EOL{
|
||||
"14.10": {Ended: true},
|
||||
"14.04": {
|
||||
ExtendedSupportUntil: time.Date(2022, 4, 1, 23, 59, 59, 0, time.UTC),
|
||||
},
|
||||
"15.04": {Ended: true},
|
||||
"16.10": {Ended: true},
|
||||
"17.04": {Ended: true},
|
||||
"17.10": {Ended: true},
|
||||
"16.04": {
|
||||
StandardSupportUntil: time.Date(2021, 4, 1, 23, 59, 59, 0, time.UTC),
|
||||
ExtendedSupportUntil: time.Date(2024, 4, 1, 23, 59, 59, 0, time.UTC),
|
||||
},
|
||||
"18.04": {
|
||||
StandardSupportUntil: time.Date(2023, 4, 1, 23, 59, 59, 0, time.UTC),
|
||||
ExtendedSupportUntil: time.Date(2028, 4, 1, 23, 59, 59, 0, time.UTC),
|
||||
},
|
||||
"18.10": {Ended: true},
|
||||
"19.04": {Ended: true},
|
||||
"19.10": {Ended: true},
|
||||
"20.04": {
|
||||
StandardSupportUntil: time.Date(2025, 4, 1, 23, 59, 59, 0, time.UTC),
|
||||
},
|
||||
"21.04": {
|
||||
StandardSupportUntil: time.Date(2022, 1, 1, 23, 59, 59, 0, time.UTC),
|
||||
},
|
||||
"21.10": {
|
||||
StandardSupportUntil: time.Date(2022, 7, 1, 23, 59, 59, 0, time.UTC),
|
||||
},
|
||||
}[release]
|
||||
case SUSEEnterpriseServer:
|
||||
//TODO
|
||||
case Alpine:
|
||||
// https://github.com/aquasecurity/trivy/blob/master/pkg/detector/ospkg/alpine/alpine.go#L19
|
||||
// https://wiki.alpinelinux.org/wiki/Alpine_Linux:Releases
|
||||
eol, found = map[string]EOL{
|
||||
"2.0": {Ended: true},
|
||||
"2.1": {Ended: true},
|
||||
"2.2": {Ended: true},
|
||||
"2.3": {Ended: true},
|
||||
"2.4": {Ended: true},
|
||||
"2.5": {Ended: true},
|
||||
"2.6": {Ended: true},
|
||||
"2.7": {Ended: true},
|
||||
"3.0": {Ended: true},
|
||||
"3.1": {Ended: true},
|
||||
"3.2": {Ended: true},
|
||||
"3.3": {Ended: true},
|
||||
"3.4": {Ended: true},
|
||||
"3.5": {Ended: true},
|
||||
"3.6": {Ended: true},
|
||||
"3.7": {Ended: true},
|
||||
"3.8": {Ended: true},
|
||||
"3.9": {Ended: true},
|
||||
"3.10": {StandardSupportUntil: time.Date(2021, 5, 1, 23, 59, 59, 0, time.UTC)},
|
||||
"3.11": {StandardSupportUntil: time.Date(2021, 11, 1, 23, 59, 59, 0, time.UTC)},
|
||||
"3.12": {StandardSupportUntil: time.Date(2022, 5, 1, 23, 59, 59, 0, time.UTC)},
|
||||
"3.13": {StandardSupportUntil: time.Date(2022, 11, 1, 23, 59, 59, 0, time.UTC)},
|
||||
}[majorDotMinor(release)]
|
||||
case FreeBSD:
|
||||
// https://www.freebsd.org/security/
|
||||
eol, found = map[string]EOL{
|
||||
"7": {Ended: true},
|
||||
"8": {Ended: true},
|
||||
"9": {Ended: true},
|
||||
"10": {Ended: true},
|
||||
"11": {StandardSupportUntil: time.Date(2021, 9, 30, 23, 59, 59, 0, time.UTC)},
|
||||
"12": {StandardSupportUntil: time.Date(2024, 6, 30, 23, 59, 59, 0, time.UTC)},
|
||||
}[major(release)]
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
func major(osVer string) (majorVersion string) {
|
||||
return strings.Split(osVer, ".")[0]
|
||||
}
|
||||
|
||||
func majorDotMinor(osVer string) (majorDotMinor string) {
|
||||
ss := strings.SplitN(osVer, ".", 3)
|
||||
if len(ss) == 1 {
|
||||
return osVer
|
||||
}
|
||||
return fmt.Sprintf("%s.%s", ss[0], ss[1])
|
||||
}
|
||||
|
||||
func isAmazonLinux1(osRelease string) bool {
|
||||
return len(strings.Fields(osRelease)) == 1
|
||||
}
|
||||
373
config/os_test.go
Normal file
373
config/os_test.go
Normal file
@@ -0,0 +1,373 @@
|
||||
package config
|
||||
|
||||
import (
|
||||
"testing"
|
||||
"time"
|
||||
)
|
||||
|
||||
func TestEOL_IsStandardSupportEnded(t *testing.T) {
|
||||
type fields struct {
|
||||
family string
|
||||
release string
|
||||
}
|
||||
tests := []struct {
|
||||
name string
|
||||
fields fields
|
||||
now time.Time
|
||||
found bool
|
||||
stdEnded bool
|
||||
extEnded bool
|
||||
}{
|
||||
// Amazon Linux
|
||||
{
|
||||
name: "amazon linux 1 supported",
|
||||
fields: fields{family: Amazon, release: "2018.03"},
|
||||
now: time.Date(2021, 1, 6, 23, 59, 59, 0, time.UTC),
|
||||
stdEnded: false,
|
||||
extEnded: false,
|
||||
found: true,
|
||||
},
|
||||
{
|
||||
name: "amazon linux 1 eol on 2023-6-30",
|
||||
fields: fields{family: Amazon, release: "2018.03"},
|
||||
now: time.Date(2023, 7, 1, 23, 59, 59, 0, time.UTC),
|
||||
stdEnded: true,
|
||||
extEnded: true,
|
||||
found: true,
|
||||
},
|
||||
{
|
||||
name: "amazon linux 2 supported",
|
||||
fields: fields{family: Amazon, release: "2 (Karoo)"},
|
||||
now: time.Date(2023, 7, 1, 23, 59, 59, 0, time.UTC),
|
||||
stdEnded: false,
|
||||
extEnded: false,
|
||||
found: true,
|
||||
},
|
||||
//RHEL
|
||||
{
|
||||
name: "RHEL7 supported",
|
||||
fields: fields{family: RedHat, release: "7"},
|
||||
now: time.Date(2021, 1, 6, 23, 59, 59, 0, time.UTC),
|
||||
stdEnded: false,
|
||||
extEnded: false,
|
||||
found: true,
|
||||
},
|
||||
{
|
||||
name: "RHEL8 supported",
|
||||
fields: fields{family: RedHat, release: "8"},
|
||||
now: time.Date(2021, 1, 6, 23, 59, 59, 0, time.UTC),
|
||||
stdEnded: false,
|
||||
extEnded: false,
|
||||
found: true,
|
||||
},
|
||||
{
|
||||
name: "RHEL6 eol",
|
||||
fields: fields{family: RedHat, release: "6"},
|
||||
now: time.Date(2021, 1, 6, 23, 59, 59, 0, time.UTC),
|
||||
stdEnded: true,
|
||||
extEnded: false,
|
||||
found: true,
|
||||
},
|
||||
{
|
||||
name: "RHEL9 not found",
|
||||
fields: fields{family: RedHat, release: "9"},
|
||||
now: time.Date(2021, 1, 6, 23, 59, 59, 0, time.UTC),
|
||||
stdEnded: false,
|
||||
extEnded: false,
|
||||
found: false,
|
||||
},
|
||||
//CentOS
|
||||
{
|
||||
name: "CentOS 7 supported",
|
||||
fields: fields{family: CentOS, release: "7"},
|
||||
now: time.Date(2021, 1, 6, 23, 59, 59, 0, time.UTC),
|
||||
stdEnded: false,
|
||||
extEnded: false,
|
||||
found: true,
|
||||
},
|
||||
{
|
||||
name: "CentOS 8 supported",
|
||||
fields: fields{family: CentOS, release: "8"},
|
||||
now: time.Date(2021, 1, 6, 23, 59, 59, 0, time.UTC),
|
||||
stdEnded: false,
|
||||
extEnded: false,
|
||||
found: true,
|
||||
},
|
||||
{
|
||||
name: "CentOS 6 eol",
|
||||
fields: fields{family: CentOS, release: "6"},
|
||||
now: time.Date(2021, 1, 6, 23, 59, 59, 0, time.UTC),
|
||||
stdEnded: true,
|
||||
extEnded: true,
|
||||
found: true,
|
||||
},
|
||||
{
|
||||
name: "CentOS 9 not found",
|
||||
fields: fields{family: CentOS, release: "9"},
|
||||
now: time.Date(2021, 1, 6, 23, 59, 59, 0, time.UTC),
|
||||
stdEnded: false,
|
||||
extEnded: false,
|
||||
found: false,
|
||||
},
|
||||
//Oracle
|
||||
{
|
||||
name: "Oracle Linux 7 supported",
|
||||
fields: fields{family: Oracle, release: "7"},
|
||||
now: time.Date(2021, 1, 6, 23, 59, 59, 0, time.UTC),
|
||||
stdEnded: false,
|
||||
extEnded: false,
|
||||
found: true,
|
||||
},
|
||||
{
|
||||
name: "Oracle Linux 8 supported",
|
||||
fields: fields{family: Oracle, release: "8"},
|
||||
now: time.Date(2021, 1, 6, 23, 59, 59, 0, time.UTC),
|
||||
stdEnded: false,
|
||||
extEnded: false,
|
||||
found: true,
|
||||
},
|
||||
{
|
||||
name: "Oracle Linux 6 eol",
|
||||
fields: fields{family: Oracle, release: "6"},
|
||||
now: time.Date(2021, 1, 6, 23, 59, 59, 0, time.UTC),
|
||||
stdEnded: false,
|
||||
extEnded: false,
|
||||
found: true,
|
||||
},
|
||||
{
|
||||
name: "Oracle Linux 9 not found",
|
||||
fields: fields{family: Oracle, release: "9"},
|
||||
now: time.Date(2021, 1, 6, 23, 59, 59, 0, time.UTC),
|
||||
stdEnded: false,
|
||||
extEnded: false,
|
||||
found: false,
|
||||
},
|
||||
//Ubuntu
|
||||
{
|
||||
name: "Ubuntu 18.04 supported",
|
||||
fields: fields{family: Ubuntu, release: "18.04"},
|
||||
now: time.Date(2021, 1, 6, 23, 59, 59, 0, time.UTC),
|
||||
stdEnded: false,
|
||||
extEnded: false,
|
||||
found: true,
|
||||
},
|
||||
{
|
||||
name: "Ubuntu 18.04 ext supported",
|
||||
fields: fields{family: Ubuntu, release: "18.04"},
|
||||
now: time.Date(2025, 1, 6, 23, 59, 59, 0, time.UTC),
|
||||
stdEnded: true,
|
||||
extEnded: false,
|
||||
found: true,
|
||||
},
|
||||
{
|
||||
name: "Ubuntu 16.04 supported",
|
||||
fields: fields{family: Ubuntu, release: "18.04"},
|
||||
now: time.Date(2021, 1, 6, 23, 59, 59, 0, time.UTC),
|
||||
stdEnded: false,
|
||||
extEnded: false,
|
||||
found: true,
|
||||
},
|
||||
{
|
||||
name: "Ubuntu 14.04 eol",
|
||||
fields: fields{family: Ubuntu, release: "14.04"},
|
||||
now: time.Date(2021, 1, 6, 23, 59, 59, 0, time.UTC),
|
||||
stdEnded: true,
|
||||
extEnded: false,
|
||||
found: true,
|
||||
},
|
||||
{
|
||||
name: "Ubuntu 14.10 eol",
|
||||
fields: fields{family: Ubuntu, release: "14.10"},
|
||||
now: time.Date(2021, 1, 6, 23, 59, 59, 0, time.UTC),
|
||||
stdEnded: true,
|
||||
extEnded: true,
|
||||
found: true,
|
||||
},
|
||||
{
|
||||
name: "Ubuntu 12.10 not found",
|
||||
fields: fields{family: Ubuntu, release: "12.10"},
|
||||
now: time.Date(2021, 1, 6, 23, 59, 59, 0, time.UTC),
|
||||
found: false,
|
||||
stdEnded: false,
|
||||
extEnded: false,
|
||||
},
|
||||
{
|
||||
name: "Ubuntu 21.04 supported",
|
||||
fields: fields{family: Ubuntu, release: "21.04"},
|
||||
now: time.Date(2021, 1, 6, 23, 59, 59, 0, time.UTC),
|
||||
found: true,
|
||||
stdEnded: false,
|
||||
extEnded: false,
|
||||
},
|
||||
//Debian
|
||||
{
|
||||
name: "Debian 9 supported",
|
||||
fields: fields{family: Debian, release: "9"},
|
||||
now: time.Date(2021, 1, 6, 23, 59, 59, 0, time.UTC),
|
||||
stdEnded: false,
|
||||
extEnded: false,
|
||||
found: true,
|
||||
},
|
||||
{
|
||||
name: "Debian 10 supported",
|
||||
fields: fields{family: Debian, release: "10"},
|
||||
now: time.Date(2021, 1, 6, 23, 59, 59, 0, time.UTC),
|
||||
stdEnded: false,
|
||||
extEnded: false,
|
||||
found: true,
|
||||
},
|
||||
{
|
||||
name: "Debian 8 supported",
|
||||
fields: fields{family: Debian, release: "8"},
|
||||
now: time.Date(2021, 1, 6, 23, 59, 59, 0, time.UTC),
|
||||
stdEnded: true,
|
||||
extEnded: true,
|
||||
found: true,
|
||||
},
|
||||
{
|
||||
name: "Debian 11 supported",
|
||||
fields: fields{family: Debian, release: "11"},
|
||||
now: time.Date(2021, 1, 6, 23, 59, 59, 0, time.UTC),
|
||||
stdEnded: false,
|
||||
extEnded: false,
|
||||
found: false,
|
||||
},
|
||||
//alpine
|
||||
{
|
||||
name: "alpine 3.10 supported",
|
||||
fields: fields{family: Alpine, release: "3.10"},
|
||||
now: time.Date(2021, 1, 6, 23, 59, 59, 0, time.UTC),
|
||||
stdEnded: false,
|
||||
extEnded: false,
|
||||
found: true,
|
||||
},
|
||||
{
|
||||
name: "Alpine 3.11 supported",
|
||||
fields: fields{family: Alpine, release: "3.11"},
|
||||
now: time.Date(2021, 1, 6, 23, 59, 59, 0, time.UTC),
|
||||
stdEnded: false,
|
||||
extEnded: false,
|
||||
found: true,
|
||||
},
|
||||
{
|
||||
name: "Alpine 3.12 supported",
|
||||
fields: fields{family: Alpine, release: "3.12"},
|
||||
now: time.Date(2021, 1, 6, 23, 59, 59, 0, time.UTC),
|
||||
stdEnded: false,
|
||||
extEnded: false,
|
||||
found: true,
|
||||
},
|
||||
{
|
||||
name: "Alpine 3.9 eol",
|
||||
fields: fields{family: Alpine, release: "3.9"},
|
||||
now: time.Date(2021, 1, 6, 23, 59, 59, 0, time.UTC),
|
||||
stdEnded: true,
|
||||
extEnded: true,
|
||||
found: true,
|
||||
},
|
||||
{
|
||||
name: "Alpine 3.14 not found",
|
||||
fields: fields{family: Alpine, release: "3.14"},
|
||||
now: time.Date(2021, 1, 6, 23, 59, 59, 0, time.UTC),
|
||||
stdEnded: false,
|
||||
extEnded: false,
|
||||
found: false,
|
||||
},
|
||||
// freebsd
|
||||
{
|
||||
name: "freebsd 11 supported",
|
||||
fields: fields{family: FreeBSD, release: "11"},
|
||||
now: time.Date(2021, 1, 6, 23, 59, 59, 0, time.UTC),
|
||||
stdEnded: false,
|
||||
extEnded: false,
|
||||
found: true,
|
||||
},
|
||||
{
|
||||
name: "freebsd 11 eol on 2021-9-30",
|
||||
fields: fields{family: FreeBSD, release: "11"},
|
||||
now: time.Date(2021, 10, 1, 23, 59, 59, 0, time.UTC),
|
||||
stdEnded: true,
|
||||
extEnded: true,
|
||||
found: true,
|
||||
},
|
||||
{
|
||||
name: "freebsd 12 supported",
|
||||
fields: fields{family: FreeBSD, release: "12"},
|
||||
now: time.Date(2021, 1, 6, 23, 59, 59, 0, time.UTC),
|
||||
stdEnded: false,
|
||||
extEnded: false,
|
||||
found: true,
|
||||
},
|
||||
{
|
||||
name: "freebsd 10 eol",
|
||||
fields: fields{family: FreeBSD, release: "10"},
|
||||
now: time.Date(2021, 1, 6, 23, 59, 59, 0, time.UTC),
|
||||
stdEnded: true,
|
||||
extEnded: true,
|
||||
found: true,
|
||||
},
|
||||
}
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
eol, found := GetEOL(tt.fields.family, tt.fields.release)
|
||||
if found != tt.found {
|
||||
t.Errorf("GetEOL.found = %v, want %v", found, tt.found)
|
||||
}
|
||||
if found {
|
||||
if got := eol.IsStandardSupportEnded(tt.now); got != tt.stdEnded {
|
||||
t.Errorf("EOL.IsStandardSupportEnded() = %v, want %v", got, tt.stdEnded)
|
||||
}
|
||||
if got := eol.IsExtendedSuppportEnded(tt.now); got != tt.extEnded {
|
||||
t.Errorf("EOL.IsExtendedSupportEnded() = %v, want %v", got, tt.extEnded)
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func Test_majorDotMinor(t *testing.T) {
|
||||
type args struct {
|
||||
osVer string
|
||||
}
|
||||
tests := []struct {
|
||||
name string
|
||||
args args
|
||||
wantMajorDotMinor string
|
||||
}{
|
||||
{
|
||||
name: "empty",
|
||||
args: args{
|
||||
osVer: "",
|
||||
},
|
||||
wantMajorDotMinor: "",
|
||||
},
|
||||
{
|
||||
name: "major",
|
||||
args: args{
|
||||
osVer: "3",
|
||||
},
|
||||
wantMajorDotMinor: "3",
|
||||
},
|
||||
{
|
||||
name: "major dot minor",
|
||||
args: args{
|
||||
osVer: "3.1",
|
||||
},
|
||||
wantMajorDotMinor: "3.1",
|
||||
},
|
||||
{
|
||||
name: "major dot minor dot release",
|
||||
args: args{
|
||||
osVer: "3.1.4",
|
||||
},
|
||||
wantMajorDotMinor: "3.1",
|
||||
},
|
||||
}
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
if gotMajorDotMinor := majorDotMinor(tt.args.osVer); gotMajorDotMinor != tt.wantMajorDotMinor {
|
||||
t.Errorf("majorDotMinor() = %v, want %v", gotMajorDotMinor, tt.wantMajorDotMinor)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
34
config/saasconf.go
Normal file
34
config/saasconf.go
Normal file
@@ -0,0 +1,34 @@
|
||||
package config
|
||||
|
||||
import (
|
||||
"github.com/asaskevich/govalidator"
|
||||
"golang.org/x/xerrors"
|
||||
)
|
||||
|
||||
// SaasConf is FutureVuls config
|
||||
type SaasConf struct {
|
||||
GroupID int64 `json:"-"`
|
||||
Token string `json:"-"`
|
||||
URL string `json:"-"`
|
||||
}
|
||||
|
||||
// Validate validates configuration
|
||||
func (c *SaasConf) Validate() (errs []error) {
|
||||
if c.GroupID == 0 {
|
||||
errs = append(errs, xerrors.New("GroupID must not be empty"))
|
||||
}
|
||||
|
||||
if len(c.Token) == 0 {
|
||||
errs = append(errs, xerrors.New("Token must not be empty"))
|
||||
}
|
||||
|
||||
if len(c.URL) == 0 {
|
||||
errs = append(errs, xerrors.New("URL must not be empty"))
|
||||
}
|
||||
|
||||
_, err := govalidator.ValidateStruct(c)
|
||||
if err != nil {
|
||||
errs = append(errs, err)
|
||||
}
|
||||
return
|
||||
}
|
||||
110
config/scanmode.go
Normal file
110
config/scanmode.go
Normal file
@@ -0,0 +1,110 @@
|
||||
package config
|
||||
|
||||
import (
|
||||
"strings"
|
||||
|
||||
"golang.org/x/xerrors"
|
||||
)
|
||||
|
||||
// ScanMode has a type of scan mode. fast, fast-root, deep and offline
|
||||
type ScanMode struct {
|
||||
flag byte
|
||||
}
|
||||
|
||||
const (
|
||||
// Fast is fast scan mode
|
||||
Fast = byte(1 << iota)
|
||||
// FastRoot is scanmode
|
||||
FastRoot
|
||||
// Deep is scanmode
|
||||
Deep
|
||||
// Offline is scanmode
|
||||
Offline
|
||||
|
||||
fastStr = "fast"
|
||||
fastRootStr = "fast-root"
|
||||
deepStr = "deep"
|
||||
offlineStr = "offline"
|
||||
)
|
||||
|
||||
// Set mode
|
||||
func (s *ScanMode) Set(f byte) {
|
||||
s.flag |= f
|
||||
}
|
||||
|
||||
// IsFast return whether scan mode is fast
|
||||
func (s ScanMode) IsFast() bool {
|
||||
return s.flag&Fast == Fast
|
||||
}
|
||||
|
||||
// IsFastRoot return whether scan mode is fastroot
|
||||
func (s ScanMode) IsFastRoot() bool {
|
||||
return s.flag&FastRoot == FastRoot
|
||||
}
|
||||
|
||||
// IsDeep return whether scan mode is deep
|
||||
func (s ScanMode) IsDeep() bool {
|
||||
return s.flag&Deep == Deep
|
||||
}
|
||||
|
||||
// IsOffline return whether scan mode is offline
|
||||
func (s ScanMode) IsOffline() bool {
|
||||
return s.flag&Offline == Offline
|
||||
}
|
||||
|
||||
func (s *ScanMode) ensure() error {
|
||||
numTrue := 0
|
||||
for _, b := range []bool{s.IsFast(), s.IsFastRoot(), s.IsDeep()} {
|
||||
if b {
|
||||
numTrue++
|
||||
}
|
||||
}
|
||||
if numTrue == 0 {
|
||||
s.Set(Fast)
|
||||
} else if s.IsDeep() && s.IsOffline() {
|
||||
return xerrors.New("Don't specify both of deep and offline")
|
||||
} else if numTrue != 1 {
|
||||
return xerrors.New("Specify only one of offline, fast, fast-root or deep")
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (s ScanMode) String() string {
|
||||
ss := ""
|
||||
if s.IsFast() {
|
||||
ss = fastStr
|
||||
} else if s.IsFastRoot() {
|
||||
ss = fastRootStr
|
||||
} else if s.IsDeep() {
|
||||
ss = deepStr
|
||||
}
|
||||
if s.IsOffline() {
|
||||
ss += " " + offlineStr
|
||||
}
|
||||
return ss + " mode"
|
||||
}
|
||||
|
||||
func setScanMode(server *ServerInfo, d ServerInfo) error {
|
||||
if len(server.ScanMode) == 0 {
|
||||
server.ScanMode = Conf.Default.ScanMode
|
||||
}
|
||||
for _, m := range server.ScanMode {
|
||||
switch strings.ToLower(m) {
|
||||
case fastStr:
|
||||
server.Mode.Set(Fast)
|
||||
case fastRootStr:
|
||||
server.Mode.Set(FastRoot)
|
||||
case deepStr:
|
||||
server.Mode.Set(Deep)
|
||||
case offlineStr:
|
||||
server.Mode.Set(Offline)
|
||||
default:
|
||||
return xerrors.Errorf("scanMode: %s of %s is invalid. Specify -fast, -fast-root, -deep or offline",
|
||||
m, server.ServerName)
|
||||
}
|
||||
}
|
||||
if err := server.Mode.ensure(); err != nil {
|
||||
return xerrors.Errorf("%s in %s", err, server.ServerName)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
97
config/scanmodule.go
Normal file
97
config/scanmodule.go
Normal file
@@ -0,0 +1,97 @@
|
||||
package config
|
||||
|
||||
import (
|
||||
"strings"
|
||||
|
||||
"golang.org/x/xerrors"
|
||||
)
|
||||
|
||||
// ScanModule has a type of scan module
|
||||
type ScanModule struct {
|
||||
flag byte
|
||||
}
|
||||
|
||||
const (
|
||||
// OSPkg is scanmodule
|
||||
OSPkg = byte(1 << iota)
|
||||
// WordPress is scanmodule
|
||||
WordPress
|
||||
// Lockfile is scanmodule
|
||||
Lockfile
|
||||
// Port is scanmodule
|
||||
Port
|
||||
|
||||
osPkgStr = "ospkg"
|
||||
wordPressStr = "wordpress"
|
||||
lockfileStr = "lockfile"
|
||||
portStr = "port"
|
||||
)
|
||||
|
||||
var allModules = []string{osPkgStr, wordPressStr, lockfileStr, portStr}
|
||||
|
||||
// Set module
|
||||
func (s *ScanModule) Set(f byte) {
|
||||
s.flag |= f
|
||||
}
|
||||
|
||||
// IsScanOSPkg return whether scanning os pkg
|
||||
func (s ScanModule) IsScanOSPkg() bool {
|
||||
return s.flag&OSPkg == OSPkg
|
||||
}
|
||||
|
||||
// IsScanWordPress return whether scanning wordpress
|
||||
func (s ScanModule) IsScanWordPress() bool {
|
||||
return s.flag&WordPress == WordPress
|
||||
}
|
||||
|
||||
// IsScanLockFile whether scanning lock file
|
||||
func (s ScanModule) IsScanLockFile() bool {
|
||||
return s.flag&Lockfile == Lockfile
|
||||
}
|
||||
|
||||
// IsScanPort whether scanning listening ports
|
||||
func (s ScanModule) IsScanPort() bool {
|
||||
return s.flag&Port == Port
|
||||
}
|
||||
|
||||
// IsZero return the struct value are all false
|
||||
func (s ScanModule) IsZero() bool {
|
||||
return !(s.IsScanOSPkg() || s.IsScanWordPress() || s.IsScanLockFile() || s.IsScanPort())
|
||||
}
|
||||
|
||||
func (s *ScanModule) ensure() error {
|
||||
if s.IsZero() {
|
||||
s.Set(OSPkg)
|
||||
s.Set(WordPress)
|
||||
s.Set(Lockfile)
|
||||
s.Set(Port)
|
||||
} else if !s.IsScanOSPkg() && s.IsScanPort() {
|
||||
return xerrors.New("When specifying the Port, Specify OSPkg as well")
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func setScanModules(server *ServerInfo, d ServerInfo) error {
|
||||
if len(server.ScanModules) == 0 {
|
||||
server.ScanModules = d.ScanModules
|
||||
}
|
||||
for _, m := range server.ScanModules {
|
||||
switch strings.ToLower(m) {
|
||||
case osPkgStr:
|
||||
server.Module.Set(OSPkg)
|
||||
case wordPressStr:
|
||||
server.Module.Set(WordPress)
|
||||
case lockfileStr:
|
||||
server.Module.Set(Lockfile)
|
||||
case portStr:
|
||||
server.Module.Set(Port)
|
||||
default:
|
||||
return xerrors.Errorf("scanMode: %s of %s is invalid. Specify %s",
|
||||
m, server.ServerName, allModules)
|
||||
}
|
||||
}
|
||||
if err := server.Module.ensure(); err != nil {
|
||||
return xerrors.Errorf("%s in %s", err, server.ServerName)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
65
config/scanmodule_test.go
Normal file
65
config/scanmodule_test.go
Normal file
@@ -0,0 +1,65 @@
|
||||
package config
|
||||
|
||||
import (
|
||||
"testing"
|
||||
)
|
||||
|
||||
func TestScanModule_IsZero(t *testing.T) {
|
||||
tests := []struct {
|
||||
name string
|
||||
modes []byte
|
||||
want bool
|
||||
}{
|
||||
{
|
||||
name: "not zero",
|
||||
modes: []byte{OSPkg},
|
||||
want: false,
|
||||
},
|
||||
{
|
||||
name: "zero",
|
||||
modes: []byte{},
|
||||
want: true,
|
||||
},
|
||||
}
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
s := ScanModule{}
|
||||
for _, b := range tt.modes {
|
||||
s.Set(b)
|
||||
}
|
||||
if got := s.IsZero(); got != tt.want {
|
||||
t.Errorf("ScanModule.IsZero() = %v, want %v", got, tt.want)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestScanModule_validate(t *testing.T) {
|
||||
tests := []struct {
|
||||
name string
|
||||
modes []byte
|
||||
wantErr bool
|
||||
}{
|
||||
{
|
||||
name: "valid",
|
||||
modes: []byte{},
|
||||
wantErr: false,
|
||||
},
|
||||
{
|
||||
name: "err",
|
||||
modes: []byte{WordPress, Lockfile, Port},
|
||||
wantErr: true,
|
||||
},
|
||||
}
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
s := ScanModule{}
|
||||
for _, b := range tt.modes {
|
||||
s.Set(b)
|
||||
}
|
||||
if err := s.ensure(); (err != nil) != tt.wantErr {
|
||||
t.Errorf("ScanModule.validate() error = %v, wantErr %v", err, tt.wantErr)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
51
config/slackconf.go
Normal file
51
config/slackconf.go
Normal file
@@ -0,0 +1,51 @@
|
||||
package config
|
||||
|
||||
import (
|
||||
"strings"
|
||||
|
||||
"github.com/asaskevich/govalidator"
|
||||
"golang.org/x/xerrors"
|
||||
)
|
||||
|
||||
// SlackConf is slack config
|
||||
type SlackConf struct {
|
||||
HookURL string `valid:"url" json:"-" toml:"hookURL,omitempty"`
|
||||
LegacyToken string `json:"-" toml:"legacyToken,omitempty"`
|
||||
Channel string `json:"-" toml:"channel,omitempty"`
|
||||
IconEmoji string `json:"-" toml:"iconEmoji,omitempty"`
|
||||
AuthUser string `json:"-" toml:"authUser,omitempty"`
|
||||
NotifyUsers []string `toml:"notifyUsers,omitempty" json:"-"`
|
||||
Text string `json:"-"`
|
||||
}
|
||||
|
||||
// Validate validates configuration
|
||||
func (c *SlackConf) Validate() (errs []error) {
|
||||
if !Conf.ToSlack {
|
||||
return
|
||||
}
|
||||
|
||||
if len(c.HookURL) == 0 && len(c.LegacyToken) == 0 {
|
||||
errs = append(errs, xerrors.New("slack.hookURL or slack.LegacyToken must not be empty"))
|
||||
}
|
||||
|
||||
if len(c.Channel) == 0 {
|
||||
errs = append(errs, xerrors.New("slack.channel must not be empty"))
|
||||
} else {
|
||||
if !(strings.HasPrefix(c.Channel, "#") ||
|
||||
c.Channel == "${servername}") {
|
||||
errs = append(errs, xerrors.Errorf(
|
||||
"channel's prefix must be '#', channel: %s", c.Channel))
|
||||
}
|
||||
}
|
||||
|
||||
if len(c.AuthUser) == 0 {
|
||||
errs = append(errs, xerrors.New("slack.authUser must not be empty"))
|
||||
}
|
||||
|
||||
_, err := govalidator.ValidateStruct(c)
|
||||
if err != nil {
|
||||
errs = append(errs, err)
|
||||
}
|
||||
|
||||
return
|
||||
}
|
||||
65
config/smtpconf.go
Normal file
65
config/smtpconf.go
Normal file
@@ -0,0 +1,65 @@
|
||||
package config
|
||||
|
||||
import (
|
||||
"github.com/asaskevich/govalidator"
|
||||
"golang.org/x/xerrors"
|
||||
)
|
||||
|
||||
// SMTPConf is smtp config
|
||||
type SMTPConf struct {
|
||||
SMTPAddr string `toml:"smtpAddr,omitempty" json:"-"`
|
||||
SMTPPort string `toml:"smtpPort,omitempty" valid:"port" json:"-"`
|
||||
User string `toml:"user,omitempty" json:"-"`
|
||||
Password string `toml:"password,omitempty" json:"-"`
|
||||
From string `toml:"from,omitempty" json:"-"`
|
||||
To []string `toml:"to,omitempty" json:"-"`
|
||||
Cc []string `toml:"cc,omitempty" json:"-"`
|
||||
SubjectPrefix string `toml:"subjectPrefix,omitempty" json:"-"`
|
||||
}
|
||||
|
||||
func checkEmails(emails []string) (errs []error) {
|
||||
for _, addr := range emails {
|
||||
if len(addr) == 0 {
|
||||
return
|
||||
}
|
||||
if ok := govalidator.IsEmail(addr); !ok {
|
||||
errs = append(errs, xerrors.Errorf("Invalid email address. email: %s", addr))
|
||||
}
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
// Validate SMTP configuration
|
||||
func (c *SMTPConf) Validate() (errs []error) {
|
||||
if !Conf.ToEmail {
|
||||
return
|
||||
}
|
||||
// Check Emails fromat
|
||||
emails := []string{}
|
||||
emails = append(emails, c.From)
|
||||
emails = append(emails, c.To...)
|
||||
emails = append(emails, c.Cc...)
|
||||
|
||||
if emailErrs := checkEmails(emails); 0 < len(emailErrs) {
|
||||
errs = append(errs, emailErrs...)
|
||||
}
|
||||
|
||||
if len(c.SMTPAddr) == 0 {
|
||||
errs = append(errs, xerrors.New("email.smtpAddr must not be empty"))
|
||||
}
|
||||
if len(c.SMTPPort) == 0 {
|
||||
errs = append(errs, xerrors.New("email.smtpPort must not be empty"))
|
||||
}
|
||||
if len(c.To) == 0 {
|
||||
errs = append(errs, xerrors.New("email.To required at least one address"))
|
||||
}
|
||||
if len(c.From) == 0 {
|
||||
errs = append(errs, xerrors.New("email.From required at least one address"))
|
||||
}
|
||||
|
||||
_, err := govalidator.ValidateStruct(c)
|
||||
if err != nil {
|
||||
errs = append(errs, err)
|
||||
}
|
||||
return
|
||||
}
|
||||
129
config/syslogconf.go
Normal file
129
config/syslogconf.go
Normal file
@@ -0,0 +1,129 @@
|
||||
package config
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"log/syslog"
|
||||
|
||||
"github.com/asaskevich/govalidator"
|
||||
"golang.org/x/xerrors"
|
||||
)
|
||||
|
||||
// SyslogConf is syslog config
|
||||
type SyslogConf struct {
|
||||
Protocol string `json:"-"`
|
||||
Host string `valid:"host" json:"-"`
|
||||
Port string `valid:"port" json:"-"`
|
||||
Severity string `json:"-"`
|
||||
Facility string `json:"-"`
|
||||
Tag string `json:"-"`
|
||||
Verbose bool `json:"-"`
|
||||
}
|
||||
|
||||
// Validate validates configuration
|
||||
func (c *SyslogConf) Validate() (errs []error) {
|
||||
if !Conf.ToSyslog {
|
||||
return nil
|
||||
}
|
||||
// If protocol is empty, it will connect to the local syslog server.
|
||||
if len(c.Protocol) > 0 && c.Protocol != "tcp" && c.Protocol != "udp" {
|
||||
errs = append(errs, errors.New(`syslog.protocol must be "tcp" or "udp"`))
|
||||
}
|
||||
|
||||
// Default port: 514
|
||||
if c.Port == "" {
|
||||
c.Port = "514"
|
||||
}
|
||||
|
||||
if _, err := c.GetSeverity(); err != nil {
|
||||
errs = append(errs, err)
|
||||
}
|
||||
|
||||
if _, err := c.GetFacility(); err != nil {
|
||||
errs = append(errs, err)
|
||||
}
|
||||
|
||||
if _, err := govalidator.ValidateStruct(c); err != nil {
|
||||
errs = append(errs, err)
|
||||
}
|
||||
return errs
|
||||
}
|
||||
|
||||
// GetSeverity gets severity
|
||||
func (c *SyslogConf) GetSeverity() (syslog.Priority, error) {
|
||||
if c.Severity == "" {
|
||||
return syslog.LOG_INFO, nil
|
||||
}
|
||||
|
||||
switch c.Severity {
|
||||
case "emerg":
|
||||
return syslog.LOG_EMERG, nil
|
||||
case "alert":
|
||||
return syslog.LOG_ALERT, nil
|
||||
case "crit":
|
||||
return syslog.LOG_CRIT, nil
|
||||
case "err":
|
||||
return syslog.LOG_ERR, nil
|
||||
case "warning":
|
||||
return syslog.LOG_WARNING, nil
|
||||
case "notice":
|
||||
return syslog.LOG_NOTICE, nil
|
||||
case "info":
|
||||
return syslog.LOG_INFO, nil
|
||||
case "debug":
|
||||
return syslog.LOG_DEBUG, nil
|
||||
default:
|
||||
return -1, xerrors.Errorf("Invalid severity: %s", c.Severity)
|
||||
}
|
||||
}
|
||||
|
||||
// GetFacility gets facility
|
||||
func (c *SyslogConf) GetFacility() (syslog.Priority, error) {
|
||||
if c.Facility == "" {
|
||||
return syslog.LOG_AUTH, nil
|
||||
}
|
||||
|
||||
switch c.Facility {
|
||||
case "kern":
|
||||
return syslog.LOG_KERN, nil
|
||||
case "user":
|
||||
return syslog.LOG_USER, nil
|
||||
case "mail":
|
||||
return syslog.LOG_MAIL, nil
|
||||
case "daemon":
|
||||
return syslog.LOG_DAEMON, nil
|
||||
case "auth":
|
||||
return syslog.LOG_AUTH, nil
|
||||
case "syslog":
|
||||
return syslog.LOG_SYSLOG, nil
|
||||
case "lpr":
|
||||
return syslog.LOG_LPR, nil
|
||||
case "news":
|
||||
return syslog.LOG_NEWS, nil
|
||||
case "uucp":
|
||||
return syslog.LOG_UUCP, nil
|
||||
case "cron":
|
||||
return syslog.LOG_CRON, nil
|
||||
case "authpriv":
|
||||
return syslog.LOG_AUTHPRIV, nil
|
||||
case "ftp":
|
||||
return syslog.LOG_FTP, nil
|
||||
case "local0":
|
||||
return syslog.LOG_LOCAL0, nil
|
||||
case "local1":
|
||||
return syslog.LOG_LOCAL1, nil
|
||||
case "local2":
|
||||
return syslog.LOG_LOCAL2, nil
|
||||
case "local3":
|
||||
return syslog.LOG_LOCAL3, nil
|
||||
case "local4":
|
||||
return syslog.LOG_LOCAL4, nil
|
||||
case "local5":
|
||||
return syslog.LOG_LOCAL5, nil
|
||||
case "local6":
|
||||
return syslog.LOG_LOCAL6, nil
|
||||
case "local7":
|
||||
return syslog.LOG_LOCAL7, nil
|
||||
default:
|
||||
return -1, xerrors.Errorf("Invalid facility: %s", c.Facility)
|
||||
}
|
||||
}
|
||||
32
config/telegramconf.go
Normal file
32
config/telegramconf.go
Normal file
@@ -0,0 +1,32 @@
|
||||
package config
|
||||
|
||||
import (
|
||||
"github.com/asaskevich/govalidator"
|
||||
"golang.org/x/xerrors"
|
||||
)
|
||||
|
||||
// TelegramConf is Telegram config
|
||||
type TelegramConf struct {
|
||||
Token string `json:"-"`
|
||||
ChatID string `json:"-"`
|
||||
}
|
||||
|
||||
// Validate validates configuration
|
||||
func (c *TelegramConf) Validate() (errs []error) {
|
||||
if !Conf.ToTelegram {
|
||||
return
|
||||
}
|
||||
if len(c.ChatID) == 0 {
|
||||
errs = append(errs, xerrors.New("TelegramConf.ChatID must not be empty"))
|
||||
}
|
||||
|
||||
if len(c.Token) == 0 {
|
||||
errs = append(errs, xerrors.New("TelegramConf.Token must not be empty"))
|
||||
}
|
||||
|
||||
_, err := govalidator.ValidateStruct(c)
|
||||
if err != nil {
|
||||
errs = append(errs, err)
|
||||
}
|
||||
return
|
||||
}
|
||||
@@ -15,270 +15,212 @@ type TOMLLoader struct {
|
||||
|
||||
// Load load the configuration TOML file specified by path arg.
|
||||
func (c TOMLLoader) Load(pathToToml, keyPass string) error {
|
||||
var conf Config
|
||||
if _, err := toml.DecodeFile(pathToToml, &conf); err != nil {
|
||||
if _, err := toml.DecodeFile(pathToToml, &Conf); err != nil {
|
||||
return err
|
||||
}
|
||||
Conf.EMail = conf.EMail
|
||||
Conf.Slack = conf.Slack
|
||||
Conf.Stride = conf.Stride
|
||||
Conf.HipChat = conf.HipChat
|
||||
Conf.ChatWork = conf.ChatWork
|
||||
Conf.Telegram = conf.Telegram
|
||||
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
|
||||
Conf.Metasploit = conf.Metasploit
|
||||
|
||||
d := conf.Default
|
||||
Conf.Default = d
|
||||
servers := make(map[string]ServerInfo)
|
||||
|
||||
if keyPass != "" {
|
||||
d.KeyPassword = keyPass
|
||||
Conf.Default.KeyPassword = keyPass
|
||||
}
|
||||
|
||||
Conf.CveDict.Init()
|
||||
Conf.OvalDict.Init()
|
||||
Conf.Gost.Init()
|
||||
Conf.Exploit.Init()
|
||||
Conf.Metasploit.Init()
|
||||
|
||||
index := 0
|
||||
for serverName, v := range conf.Servers {
|
||||
if 0 < len(v.KeyPassword) {
|
||||
return xerrors.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)
|
||||
for name, server := range Conf.Servers {
|
||||
server.ServerName = name
|
||||
if 0 < len(server.KeyPassword) {
|
||||
return xerrors.Errorf("[Deprecated] KEYPASSWORD IN CONFIG FILE ARE UNSECURE. REMOVE THEM IMMEDIATELY FOR A SECURITY REASONS. THEY WILL BE REMOVED IN A FUTURE RELEASE: %s", name)
|
||||
}
|
||||
|
||||
s := ServerInfo{ServerName: serverName}
|
||||
if v.Type != ServerTypePseudo {
|
||||
s.Host = v.Host
|
||||
if len(s.Host) == 0 {
|
||||
return xerrors.Errorf("%s is invalid. host is empty", serverName)
|
||||
}
|
||||
|
||||
s.JumpServer = v.JumpServer
|
||||
if len(s.JumpServer) == 0 {
|
||||
s.JumpServer = d.JumpServer
|
||||
}
|
||||
|
||||
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 xerrors.Errorf("%s is invalid. User is empty", serverName)
|
||||
}
|
||||
}
|
||||
|
||||
s.SSHConfigPath = v.SSHConfigPath
|
||||
if len(s.SSHConfigPath) == 0 {
|
||||
s.SSHConfigPath = d.SSHConfigPath
|
||||
}
|
||||
|
||||
s.KeyPath = v.KeyPath
|
||||
if len(s.KeyPath) == 0 {
|
||||
s.KeyPath = d.KeyPath
|
||||
}
|
||||
s.KeyPassword = v.KeyPassword
|
||||
if len(s.KeyPassword) == 0 {
|
||||
s.KeyPassword = d.KeyPassword
|
||||
}
|
||||
if err := setDefaultIfEmpty(&server, Conf.Default); err != nil {
|
||||
return xerrors.Errorf("Failed to set default value to config. server: %s, err: %w", name, err)
|
||||
}
|
||||
|
||||
s.ScanMode = v.ScanMode
|
||||
if len(s.ScanMode) == 0 {
|
||||
s.ScanMode = d.ScanMode
|
||||
if len(s.ScanMode) == 0 {
|
||||
s.ScanMode = []string{"fast"}
|
||||
}
|
||||
}
|
||||
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 xerrors.Errorf("scanMode: %s of %s is invalid. Specify -fast, -fast-root, -deep or offline", m, serverName)
|
||||
}
|
||||
}
|
||||
if err := s.Mode.validate(); err != nil {
|
||||
return xerrors.Errorf("%s in %s", err, serverName)
|
||||
if err := setScanMode(&server, Conf.Default); err != nil {
|
||||
return xerrors.Errorf("Failed to set ScanMode: %w", err)
|
||||
}
|
||||
|
||||
s.CpeNames = v.CpeNames
|
||||
if len(s.CpeNames) == 0 {
|
||||
s.CpeNames = d.CpeNames
|
||||
if err := setScanModules(&server, Conf.Default); err != nil {
|
||||
return xerrors.Errorf("Failed to set ScanModule: %w", err)
|
||||
}
|
||||
|
||||
s.Lockfiles = v.Lockfiles
|
||||
if len(s.Lockfiles) == 0 {
|
||||
s.Lockfiles = d.Lockfiles
|
||||
if len(server.CpeNames) == 0 {
|
||||
server.CpeNames = Conf.Default.CpeNames
|
||||
}
|
||||
|
||||
s.FindLock = v.FindLock
|
||||
|
||||
for i, n := range s.CpeNames {
|
||||
for i, n := range server.CpeNames {
|
||||
uri, err := toCpeURI(n)
|
||||
if err != nil {
|
||||
return xerrors.Errorf("Failed to parse CPENames %s in %s, err: %w", n, serverName, err)
|
||||
return xerrors.Errorf("Failed to parse CPENames %s in %s, err: %w", n, name, err)
|
||||
}
|
||||
s.CpeNames[i] = uri
|
||||
server.CpeNames[i] = uri
|
||||
}
|
||||
|
||||
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
|
||||
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 xerrors.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
|
||||
for _, cve := range d.IgnoreCves {
|
||||
for _, cve := range Conf.Default.IgnoreCves {
|
||||
found := false
|
||||
for _, c := range s.IgnoreCves {
|
||||
for _, c := range server.IgnoreCves {
|
||||
if cve == c {
|
||||
found = true
|
||||
break
|
||||
}
|
||||
}
|
||||
if !found {
|
||||
s.IgnoreCves = append(s.IgnoreCves, cve)
|
||||
server.IgnoreCves = append(server.IgnoreCves, cve)
|
||||
}
|
||||
}
|
||||
|
||||
s.IgnorePkgsRegexp = v.IgnorePkgsRegexp
|
||||
for _, pkg := range d.IgnorePkgsRegexp {
|
||||
for _, pkg := range Conf.Default.IgnorePkgsRegexp {
|
||||
found := false
|
||||
for _, p := range s.IgnorePkgsRegexp {
|
||||
for _, p := range server.IgnorePkgsRegexp {
|
||||
if pkg == p {
|
||||
found = true
|
||||
break
|
||||
}
|
||||
}
|
||||
if !found {
|
||||
s.IgnorePkgsRegexp = append(s.IgnorePkgsRegexp, pkg)
|
||||
server.IgnorePkgsRegexp = append(server.IgnorePkgsRegexp, pkg)
|
||||
}
|
||||
}
|
||||
for _, reg := range s.IgnorePkgsRegexp {
|
||||
for _, reg := range server.IgnorePkgsRegexp {
|
||||
_, err := regexp.Compile(reg)
|
||||
if err != nil {
|
||||
return xerrors.Errorf("Failed to parse %s in %s. err: %w", reg, serverName, err)
|
||||
return xerrors.Errorf("Failed to parse %s in %s. err: %w", reg, name, err)
|
||||
}
|
||||
}
|
||||
for contName, cont := range s.Containers {
|
||||
for contName, cont := range server.Containers {
|
||||
for _, reg := range cont.IgnorePkgsRegexp {
|
||||
_, err := regexp.Compile(reg)
|
||||
if err != nil {
|
||||
return xerrors.Errorf("Failed to parse %s in %s@%s. err: %w",
|
||||
reg, contName, serverName, err)
|
||||
reg, contName, name, err)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
opt := map[string]interface{}{}
|
||||
for k, v := range d.Optional {
|
||||
opt[k] = v
|
||||
for ownerRepo, githubSetting := range server.GitHubRepos {
|
||||
if ss := strings.Split(ownerRepo, "/"); len(ss) != 2 {
|
||||
return xerrors.Errorf("Failed to parse GitHub owner/repo: %s in %s",
|
||||
ownerRepo, name)
|
||||
}
|
||||
if githubSetting.Token == "" {
|
||||
return xerrors.Errorf("GitHub owner/repo: %s in %s token is empty",
|
||||
ownerRepo, name)
|
||||
}
|
||||
}
|
||||
for k, v := range v.Optional {
|
||||
opt[k] = v
|
||||
}
|
||||
s.Optional = opt
|
||||
|
||||
s.Enablerepo = v.Enablerepo
|
||||
if len(s.Enablerepo) == 0 {
|
||||
s.Enablerepo = d.Enablerepo
|
||||
if len(server.Enablerepo) == 0 {
|
||||
server.Enablerepo = Conf.Default.Enablerepo
|
||||
}
|
||||
if len(s.Enablerepo) != 0 {
|
||||
for _, repo := range s.Enablerepo {
|
||||
if len(server.Enablerepo) != 0 {
|
||||
for _, repo := range server.Enablerepo {
|
||||
switch repo {
|
||||
case "base", "updates":
|
||||
// nop
|
||||
default:
|
||||
return xerrors.Errorf(
|
||||
"For now, enablerepo have to be base or updates: %s, servername: %s",
|
||||
s.Enablerepo, serverName)
|
||||
"For now, enablerepo have to be base or updates: %s",
|
||||
server.Enablerepo)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
s.GitHubRepos = v.GitHubRepos
|
||||
for ownerRepo, githubSetting := range s.GitHubRepos {
|
||||
if ss := strings.Split(ownerRepo, "/"); len(ss) != 2 {
|
||||
return xerrors.Errorf("Failed to parse GitHub owner/repo: %s in %s",
|
||||
ownerRepo, serverName)
|
||||
}
|
||||
if githubSetting.Token == "" {
|
||||
return xerrors.Errorf("GitHub owner/repo: %s in %s token is empty",
|
||||
ownerRepo, serverName)
|
||||
}
|
||||
}
|
||||
|
||||
s.UUIDs = v.UUIDs
|
||||
s.Type = v.Type
|
||||
|
||||
s.WordPress.WPVulnDBToken = v.WordPress.WPVulnDBToken
|
||||
s.WordPress.CmdPath = v.WordPress.CmdPath
|
||||
s.WordPress.DocRoot = v.WordPress.DocRoot
|
||||
s.WordPress.OSUser = v.WordPress.OSUser
|
||||
s.WordPress.IgnoreInactive = v.WordPress.IgnoreInactive
|
||||
|
||||
s.IgnoredJSONKeys = v.IgnoredJSONKeys
|
||||
if len(s.IgnoredJSONKeys) == 0 {
|
||||
s.IgnoredJSONKeys = d.IgnoredJSONKeys
|
||||
}
|
||||
|
||||
s.LogMsgAnsiColor = Colors[index%len(Colors)]
|
||||
server.LogMsgAnsiColor = Colors[index%len(Colors)]
|
||||
index++
|
||||
|
||||
servers[serverName] = s
|
||||
Conf.Servers[name] = server
|
||||
}
|
||||
Conf.Servers = servers
|
||||
return nil
|
||||
}
|
||||
|
||||
func setDefaultIfEmpty(server *ServerInfo, d ServerInfo) error {
|
||||
if server.Type != ServerTypePseudo {
|
||||
if len(server.Host) == 0 {
|
||||
return xerrors.Errorf("server.host is empty")
|
||||
}
|
||||
|
||||
if len(server.JumpServer) == 0 {
|
||||
server.JumpServer = Conf.Default.JumpServer
|
||||
}
|
||||
|
||||
if server.Port == "" {
|
||||
if Conf.Default.Port != "" {
|
||||
server.Port = Conf.Default.Port
|
||||
} else {
|
||||
server.Port = "22"
|
||||
}
|
||||
}
|
||||
|
||||
if server.User == "" {
|
||||
server.User = Conf.Default.User
|
||||
if server.User == "" && server.Port != "local" {
|
||||
return xerrors.Errorf("server.user is empty")
|
||||
}
|
||||
}
|
||||
|
||||
if server.SSHConfigPath == "" {
|
||||
server.SSHConfigPath = Conf.Default.SSHConfigPath
|
||||
}
|
||||
|
||||
if server.KeyPath == "" {
|
||||
server.KeyPath = Conf.Default.KeyPath
|
||||
}
|
||||
|
||||
if server.KeyPassword == "" {
|
||||
server.KeyPassword = Conf.Default.KeyPassword
|
||||
}
|
||||
}
|
||||
|
||||
if len(server.Lockfiles) == 0 {
|
||||
server.Lockfiles = Conf.Default.Lockfiles
|
||||
}
|
||||
|
||||
if len(server.ContainersIncluded) == 0 {
|
||||
server.ContainersIncluded = Conf.Default.ContainersIncluded
|
||||
}
|
||||
|
||||
if len(server.ContainersExcluded) == 0 {
|
||||
server.ContainersExcluded = Conf.Default.ContainersExcluded
|
||||
}
|
||||
|
||||
if server.ContainerType == "" {
|
||||
server.ContainerType = Conf.Default.ContainerType
|
||||
}
|
||||
|
||||
for contName, cont := range server.Containers {
|
||||
cont.IgnoreCves = append(cont.IgnoreCves, Conf.Default.IgnoreCves...)
|
||||
server.Containers[contName] = cont
|
||||
}
|
||||
|
||||
if server.OwaspDCXMLPath == "" {
|
||||
server.OwaspDCXMLPath = Conf.Default.OwaspDCXMLPath
|
||||
}
|
||||
|
||||
if server.Memo == "" {
|
||||
server.Memo = Conf.Default.Memo
|
||||
}
|
||||
|
||||
if server.WordPress == nil {
|
||||
server.WordPress = Conf.Default.WordPress
|
||||
if server.WordPress == nil {
|
||||
server.WordPress = &WordPressConf{}
|
||||
}
|
||||
}
|
||||
|
||||
if len(server.IgnoredJSONKeys) == 0 {
|
||||
server.IgnoredJSONKeys = Conf.Default.IgnoredJSONKeys
|
||||
}
|
||||
|
||||
opt := map[string]interface{}{}
|
||||
for k, v := range Conf.Default.Optional {
|
||||
opt[k] = v
|
||||
}
|
||||
for k, v := range server.Optional {
|
||||
opt[k] = v
|
||||
}
|
||||
server.Optional = opt
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
|
||||
@@ -10,7 +10,7 @@ import (
|
||||
|
||||
"github.com/future-architect/vuls/config"
|
||||
"github.com/future-architect/vuls/models"
|
||||
"github.com/future-architect/vuls/report"
|
||||
"github.com/future-architect/vuls/saas"
|
||||
"github.com/spf13/cobra"
|
||||
)
|
||||
|
||||
@@ -73,7 +73,7 @@ func main() {
|
||||
config.Conf.Saas.GroupID = groupID
|
||||
config.Conf.Saas.Token = token
|
||||
config.Conf.Saas.URL = url
|
||||
if err = (report.SaasWriter{}).Write(scanResult); err != nil {
|
||||
if err = (saas.Writer{}).Write(scanResult); err != nil {
|
||||
fmt.Println(err)
|
||||
os.Exit(1)
|
||||
return
|
||||
|
||||
@@ -16,6 +16,9 @@ func (e Error) Error() string {
|
||||
var (
|
||||
// ErrFailedToAccessGithubAPI is error of github alert's api access
|
||||
ErrFailedToAccessGithubAPI ErrorCode = "ErrFailedToAccessGithubAPI"
|
||||
|
||||
// ErrFailedToAccessWpScan is error of wpscan.com api access
|
||||
ErrFailedToAccessWpScan ErrorCode = "ErrFailedToAccessWpScan"
|
||||
)
|
||||
|
||||
// New :
|
||||
|
||||
@@ -1,27 +1,25 @@
|
||||
// +build !scanner
|
||||
|
||||
package exploit
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"net/http"
|
||||
|
||||
cnf "github.com/future-architect/vuls/config"
|
||||
"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"
|
||||
"golang.org/x/xerrors"
|
||||
"github.com/vulsio/go-exploitdb/db"
|
||||
exploitmodels "github.com/vulsio/go-exploitdb/models"
|
||||
)
|
||||
|
||||
// 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() {
|
||||
func FillWithExploit(driver db.DB, r *models.ScanResult, cnf *config.ExploitConf) (nExploitCve int, err error) {
|
||||
if cnf.IsFetchViaHTTP() {
|
||||
var cveIDs []string
|
||||
for cveID := range r.ScannedCves {
|
||||
cveIDs = append(cveIDs, cveID)
|
||||
}
|
||||
prefix, _ := util.URLPathJoin(cnf.Conf.Exploit.URL, "cves")
|
||||
prefix, _ := util.URLPathJoin(cnf.URL, "cves")
|
||||
responses, err := getCvesViaHTTP(cveIDs, prefix)
|
||||
if err != nil {
|
||||
return 0, err
|
||||
@@ -85,33 +83,3 @@ func ConvertToModels(es []*exploitmodels.Exploit) (exploits []models.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 xerrors.Errorf("Failed to connect to exploit server. url: %s, errs: %w", 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
|
||||
}
|
||||
|
||||
@@ -91,7 +91,7 @@ func httpGet(url string, req request, resChan chan<- response, errChan chan<- er
|
||||
if count == retryMax {
|
||||
return nil
|
||||
}
|
||||
return xerrors.Errorf("HTTP GET error, url: %s, resp: %v, err: %w", url, resp, errs)
|
||||
return xerrors.Errorf("HTTP GET error, url: %s, resp: %v, err: %s", url, resp, errs)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
@@ -15,9 +15,10 @@ import (
|
||||
"golang.org/x/oauth2"
|
||||
)
|
||||
|
||||
// FillGitHubSecurityAlerts access to owner/repo on GitHub and fetch security alerts of the repository via GitHub API v4 GraphQL and then set to the given ScanResult.
|
||||
// DetectGitHubSecurityAlerts access to owner/repo on GitHub and fetch security alerts of the repository via GitHub API v4 GraphQL and then set to the given ScanResult.
|
||||
// https://help.github.com/articles/about-security-alerts-for-vulnerable-dependencies/
|
||||
func FillGitHubSecurityAlerts(r *models.ScanResult, owner, repo, token string) (nCVEs int, err error) {
|
||||
//TODO move to report
|
||||
func DetectGitHubSecurityAlerts(r *models.ScanResult, owner, repo, token string) (nCVEs int, err error) {
|
||||
src := oauth2.StaticTokenSource(
|
||||
&oauth2.Token{AccessToken: token},
|
||||
)
|
||||
@@ -65,10 +66,8 @@ func FillGitHubSecurityAlerts(r *models.ScanResult, owner, repo, token string) (
|
||||
// util.Log.Debugf("%s", pp.Sprint(alerts))
|
||||
// util.Log.Debugf("%s", string(body))
|
||||
if alerts.Data.Repository.URL == "" {
|
||||
return 0, errof.New(
|
||||
errof.ErrFailedToAccessGithubAPI,
|
||||
fmt.Sprintf("Failed to access to GitHub API. Response: %s", string(body)),
|
||||
)
|
||||
return 0, errof.New(errof.ErrFailedToAccessGithubAPI,
|
||||
fmt.Sprintf("Failed to access to GitHub API. Response: %s", string(body)))
|
||||
}
|
||||
|
||||
for _, v := range alerts.Data.Repository.VulnerabilityAlerts.Edges {
|
||||
@@ -102,20 +101,39 @@ func FillGitHubSecurityAlerts(r *models.ScanResult, owner, repo, token string) (
|
||||
cveIDs = other
|
||||
}
|
||||
|
||||
refs := []models.Reference{}
|
||||
for _, r := range v.Node.SecurityAdvisory.References {
|
||||
refs = append(refs, models.Reference{Link: r.URL})
|
||||
}
|
||||
|
||||
for _, cveID := range cveIDs {
|
||||
cveContent := models.CveContent{
|
||||
Type: models.GitHub,
|
||||
CveID: cveID,
|
||||
Title: v.Node.SecurityAdvisory.Summary,
|
||||
Summary: v.Node.SecurityAdvisory.Description,
|
||||
Cvss2Severity: v.Node.SecurityVulnerability.Severity,
|
||||
Cvss3Severity: v.Node.SecurityVulnerability.Severity,
|
||||
SourceLink: v.Node.SecurityAdvisory.Permalink,
|
||||
References: refs,
|
||||
Published: v.Node.SecurityAdvisory.PublishedAt,
|
||||
LastModified: v.Node.SecurityAdvisory.UpdatedAt,
|
||||
}
|
||||
|
||||
if val, ok := r.ScannedCves[cveID]; ok {
|
||||
val.GitHubSecurityAlerts = val.GitHubSecurityAlerts.Add(m)
|
||||
val.CveContents[models.GitHub] = cveContent
|
||||
r.ScannedCves[cveID] = val
|
||||
nCVEs++
|
||||
} else {
|
||||
v := models.VulnInfo{
|
||||
CveID: cveID,
|
||||
Confidences: models.Confidences{models.GitHubMatch},
|
||||
GitHubSecurityAlerts: models.GitHubSecurityAlerts{m},
|
||||
CveContents: models.NewCveContents(cveContent),
|
||||
}
|
||||
r.ScannedCves[cveID] = v
|
||||
nCVEs++
|
||||
}
|
||||
nCVEs++
|
||||
}
|
||||
}
|
||||
if !alerts.Data.Repository.VulnerabilityAlerts.PageInfo.HasNextPage {
|
||||
|
||||
57
go.mod
57
go.mod
@@ -1,67 +1,50 @@
|
||||
module github.com/future-architect/vuls
|
||||
|
||||
go 1.14
|
||||
|
||||
replace (
|
||||
gopkg.in/mattn/go-colorable.v0 => github.com/mattn/go-colorable v0.1.0
|
||||
gopkg.in/mattn/go-isatty.v0 => github.com/mattn/go-isatty v0.0.6
|
||||
)
|
||||
go 1.15
|
||||
|
||||
require (
|
||||
github.com/Azure/azure-sdk-for-go v43.3.0+incompatible
|
||||
github.com/Azure/azure-sdk-for-go v50.2.0+incompatible
|
||||
github.com/BurntSushi/toml v0.3.1
|
||||
github.com/Masterminds/semver/v3 v3.1.0
|
||||
github.com/RackSec/srslog v0.0.0-20180709174129-a4725f04ec91
|
||||
github.com/aquasecurity/fanal v0.0.0-20200820074632-6de62ef86882
|
||||
github.com/aquasecurity/trivy v0.12.0
|
||||
github.com/aquasecurity/trivy-db v0.0.0-20200826140828-6da6467703aa
|
||||
github.com/aquasecurity/fanal v0.0.0-20210119051230-28c249da7cfd
|
||||
github.com/aquasecurity/trivy v0.15.0
|
||||
github.com/aquasecurity/trivy-db v0.0.0-20210121143430-2a5c54036a86
|
||||
github.com/asaskevich/govalidator v0.0.0-20200907205600-7a23bdc65eef
|
||||
github.com/aws/aws-sdk-go v1.33.21
|
||||
github.com/aws/aws-sdk-go v1.36.31
|
||||
github.com/boltdb/bolt v1.3.1
|
||||
github.com/cenkalti/backoff v2.2.1+incompatible
|
||||
github.com/d4l3k/messagediff v1.2.2-0.20190829033028-7e0a312ae40b
|
||||
github.com/emersion/go-sasl v0.0.0-20200509203442-7bfe0ed36a21
|
||||
github.com/emersion/go-smtp v0.13.0
|
||||
github.com/go-redis/redis v6.15.9+incompatible // indirect
|
||||
github.com/emersion/go-smtp v0.14.0
|
||||
github.com/google/subcommands v1.2.0
|
||||
github.com/gosuri/uitable v0.0.4
|
||||
github.com/hashicorp/go-uuid v1.0.2
|
||||
github.com/hashicorp/go-version v1.2.1
|
||||
github.com/howeyc/gopass v0.0.0-20190910152052-7cb4b85ec19c
|
||||
github.com/inconshreveable/log15 v0.0.0-20201112154412-8562bdadbbac // indirect
|
||||
github.com/jesseduffield/gocui v0.3.0
|
||||
github.com/jinzhu/gorm v1.9.16 // indirect
|
||||
github.com/k0kubun/pp v3.0.1+incompatible
|
||||
github.com/knqyf263/go-apk-version v0.0.0-20200609155635-041fdbb8563f
|
||||
github.com/knqyf263/go-cpe v0.0.0-20180327054844-659663f6eca2
|
||||
github.com/knqyf263/go-cpe v0.0.0-20201213041631-54f6ab28673f
|
||||
github.com/knqyf263/go-deb-version v0.0.0-20190517075300-09fca494f03d
|
||||
github.com/knqyf263/go-rpm-version v0.0.0-20170716094938-74609b86c936
|
||||
github.com/knqyf263/gost v0.1.4
|
||||
github.com/kotakanbe/go-cve-dictionary v0.5.0
|
||||
github.com/knqyf263/gost v0.1.7
|
||||
github.com/kotakanbe/go-cve-dictionary v0.5.7
|
||||
github.com/kotakanbe/go-pingscanner v0.1.0
|
||||
github.com/kotakanbe/goval-dictionary v0.2.14
|
||||
github.com/kotakanbe/goval-dictionary v0.3.1
|
||||
github.com/kotakanbe/logrus-prefixed-formatter v0.0.0-20180123152602-928f7356cb96
|
||||
github.com/lib/pq v1.8.0 // indirect
|
||||
github.com/mattn/go-colorable v0.1.8 // indirect
|
||||
github.com/mgutz/ansi v0.0.0-20170206155736-9520e82c474b // indirect
|
||||
github.com/mgutz/ansi v0.0.0-20200706080929-d51e80ef957d // indirect
|
||||
github.com/mitchellh/go-homedir v1.1.0
|
||||
github.com/mozqnet/go-exploitdb v0.1.0
|
||||
github.com/nlopes/slack v0.6.0
|
||||
github.com/nsf/termbox-go v0.0.0-20200418040025-38ba6e5628f1 // indirect
|
||||
github.com/olekukonko/tablewriter v0.0.4
|
||||
github.com/parnurzeal/gorequest v0.2.16
|
||||
github.com/rifflock/lfshook v0.0.0-20180920164130-b9218ef580f5
|
||||
github.com/sirupsen/logrus v1.6.0
|
||||
github.com/spf13/afero v1.3.0
|
||||
github.com/spf13/cobra v1.0.0
|
||||
github.com/takuzoo3868/go-msfdb v0.1.1
|
||||
github.com/valyala/fasttemplate v1.2.1 // indirect
|
||||
golang.org/x/crypto v0.0.0-20201117144127-c1f2f97bffc9
|
||||
golang.org/x/lint v0.0.0-20200302205851-738671d3881b // indirect
|
||||
golang.org/x/net v0.0.0-20201110031124-69a78807bb2b // indirect
|
||||
golang.org/x/oauth2 v0.0.0-20200107190931-bf48bf16ab8d
|
||||
golang.org/x/sys v0.0.0-20201117222635-ba5294a509c7 // indirect
|
||||
golang.org/x/text v0.3.4 // indirect
|
||||
github.com/sirupsen/logrus v1.7.0
|
||||
github.com/spf13/afero v1.5.1
|
||||
github.com/spf13/cobra v1.1.1
|
||||
github.com/takuzoo3868/go-msfdb v0.1.3
|
||||
github.com/vulsio/go-exploitdb v0.1.4
|
||||
golang.org/x/crypto v0.0.0-20201221181555-eec23a3978ad
|
||||
golang.org/x/oauth2 v0.0.0-20210125201302-af13f521f196
|
||||
golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1
|
||||
k8s.io/utils v0.0.0-20200619165400-6e3d28b6ed19
|
||||
k8s.io/utils v0.0.0-20210111153108-fddb29f9d009
|
||||
)
|
||||
|
||||
40
gost/base.go
40
gost/base.go
@@ -1,14 +1,10 @@
|
||||
// +build !scanner
|
||||
|
||||
package gost
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"net/http"
|
||||
|
||||
cnf "github.com/future-architect/vuls/config"
|
||||
"github.com/future-architect/vuls/models"
|
||||
"github.com/knqyf263/gost/db"
|
||||
"github.com/parnurzeal/gorequest"
|
||||
"golang.org/x/xerrors"
|
||||
)
|
||||
|
||||
// Base is a base struct
|
||||
@@ -17,35 +13,5 @@ type Base struct {
|
||||
|
||||
// FillCVEsWithRedHat fills cve information that has in Gost
|
||||
func (b Base) FillCVEsWithRedHat(driver db.DB, r *models.ScanResult) error {
|
||||
return RedHat{}.fillFixed(driver, r)
|
||||
}
|
||||
|
||||
// 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 xerrors.Errorf("Failed to connect to gost server. url: %s, errs: %w", 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
|
||||
return RedHat{}.fillCvesWithRedHatAPI(driver, r)
|
||||
}
|
||||
|
||||
@@ -1,3 +1,5 @@
|
||||
// +build !scanner
|
||||
|
||||
package gost
|
||||
|
||||
import (
|
||||
@@ -21,7 +23,7 @@ type packCves struct {
|
||||
cves []models.CveContent
|
||||
}
|
||||
|
||||
func (deb Debian) Supported(major string) bool {
|
||||
func (deb Debian) supported(major string) bool {
|
||||
_, ok := map[string]string{
|
||||
"8": "jessie",
|
||||
"9": "stretch",
|
||||
@@ -32,7 +34,7 @@ func (deb Debian) Supported(major string) bool {
|
||||
|
||||
// DetectUnfixed fills cve information that has in Gost
|
||||
func (deb Debian) DetectUnfixed(driver db.DB, r *models.ScanResult, _ bool) (nCVEs int, err error) {
|
||||
if !deb.Supported(major(r.Release)) {
|
||||
if !deb.supported(major(r.Release)) {
|
||||
// only logging
|
||||
util.Log.Warnf("Debian %s is not supported yet", r.Release)
|
||||
return 0, nil
|
||||
|
||||
@@ -53,7 +53,7 @@ func TestDebian_Supported(t *testing.T) {
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
deb := Debian{}
|
||||
if got := deb.Supported(tt.args.major); got != tt.want {
|
||||
if got := deb.supported(tt.args.major); got != tt.want {
|
||||
t.Errorf("Debian.Supported() = %v, want %v", got, tt.want)
|
||||
}
|
||||
})
|
||||
|
||||
@@ -1,3 +1,5 @@
|
||||
// +build !scanner
|
||||
|
||||
package gost
|
||||
|
||||
import (
|
||||
@@ -10,12 +12,6 @@ import (
|
||||
type Client interface {
|
||||
DetectUnfixed(db.DB, *models.ScanResult, bool) (int, error)
|
||||
FillCVEsWithRedHat(db.DB, *models.ScanResult) 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
|
||||
|
||||
@@ -1,3 +1,5 @@
|
||||
// +build !scanner
|
||||
|
||||
package gost
|
||||
|
||||
import (
|
||||
@@ -26,19 +28,20 @@ func (ms Microsoft) DetectUnfixed(driver db.DB, r *models.ScanResult, _ bool) (n
|
||||
if _, ok := r.ScannedCves[cveID]; !ok {
|
||||
continue
|
||||
}
|
||||
cveCont := ms.ConvertToModel(&msCve)
|
||||
cveCont, mitigations := ms.ConvertToModel(&msCve)
|
||||
v, _ := r.ScannedCves[cveID]
|
||||
if v.CveContents == nil {
|
||||
v.CveContents = models.CveContents{}
|
||||
}
|
||||
v.CveContents[models.Microsoft] = *cveCont
|
||||
v.Mitigations = append(v.Mitigations, mitigations...)
|
||||
r.ScannedCves[cveID] = v
|
||||
}
|
||||
return len(cveIDs), nil
|
||||
}
|
||||
|
||||
// ConvertToModel converts gost model to vuls model
|
||||
func (ms Microsoft) ConvertToModel(cve *gostmodels.MicrosoftCVE) *models.CveContent {
|
||||
func (ms Microsoft) ConvertToModel(cve *gostmodels.MicrosoftCVE) (*models.CveContent, []models.Mitigation) {
|
||||
v3score := 0.0
|
||||
var v3Vector string
|
||||
for _, scoreSet := range cve.ScoreSets {
|
||||
@@ -80,6 +83,18 @@ func (ms Microsoft) ConvertToModel(cve *gostmodels.MicrosoftCVE) *models.CveCont
|
||||
option["kbids"] = strings.Join(kbids, ",")
|
||||
}
|
||||
|
||||
vendorURL := "https://msrc.microsoft.com/update-guide/vulnerability/" + cve.CveID
|
||||
mitigations := []models.Mitigation{}
|
||||
if cve.Mitigation != "" {
|
||||
mitigations = []models.Mitigation{
|
||||
{
|
||||
CveContentType: models.Microsoft,
|
||||
Mitigation: cve.Mitigation,
|
||||
URL: vendorURL,
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
return &models.CveContent{
|
||||
Type: models.Microsoft,
|
||||
CveID: cve.CveID,
|
||||
@@ -90,10 +105,9 @@ func (ms Microsoft) ConvertToModel(cve *gostmodels.MicrosoftCVE) *models.CveCont
|
||||
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,
|
||||
SourceLink: vendorURL,
|
||||
Optional: option,
|
||||
}
|
||||
}, mitigations
|
||||
}
|
||||
|
||||
@@ -1,8 +1,8 @@
|
||||
// +build !scanner
|
||||
|
||||
package gost
|
||||
|
||||
import (
|
||||
"strings"
|
||||
|
||||
"github.com/future-architect/vuls/models"
|
||||
"github.com/knqyf263/gost/db"
|
||||
)
|
||||
@@ -16,7 +16,3 @@ type Pseudo struct {
|
||||
func (pse Pseudo) DetectUnfixed(driver db.DB, r *models.ScanResult, _ bool) (int, error) {
|
||||
return 0, nil
|
||||
}
|
||||
|
||||
func major(osVer string) (majorVersion string) {
|
||||
return strings.Split(osVer, ".")[0]
|
||||
}
|
||||
|
||||
210
gost/redhat.go
210
gost/redhat.go
@@ -1,3 +1,5 @@
|
||||
// +build !scanner
|
||||
|
||||
package gost
|
||||
|
||||
import (
|
||||
@@ -19,10 +21,47 @@ type RedHat struct {
|
||||
|
||||
// DetectUnfixed fills cve information that has in Gost
|
||||
func (red RedHat) DetectUnfixed(driver db.DB, r *models.ScanResult, ignoreWillNotFix bool) (nCVEs int, err error) {
|
||||
return red.fillUnfixed(driver, r, ignoreWillNotFix)
|
||||
return red.detectUnfixed(driver, r, ignoreWillNotFix)
|
||||
}
|
||||
|
||||
func (red RedHat) fillFixed(driver db.DB, r *models.ScanResult) error {
|
||||
func (red RedHat) detectUnfixed(driver db.DB, r *models.ScanResult, ignoreWillNotFix bool) (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 {
|
||||
if newly := red.setUnfixedCveToScanResult(&cve, r); newly {
|
||||
nCVEs++
|
||||
}
|
||||
}
|
||||
}
|
||||
} else {
|
||||
if driver == nil {
|
||||
return 0, nil
|
||||
}
|
||||
for _, pack := range r.Packages {
|
||||
// CVE-ID: RedhatCVE
|
||||
cves := driver.GetUnfixedCvesRedhat(major(r.Release), pack.Name, ignoreWillNotFix)
|
||||
for _, cve := range cves {
|
||||
if newly := red.setUnfixedCveToScanResult(&cve, r); newly {
|
||||
nCVEs++
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
return nCVEs, nil
|
||||
}
|
||||
|
||||
func (red RedHat) fillCvesWithRedHatAPI(driver db.DB, r *models.ScanResult) error {
|
||||
cveIDs := []string{}
|
||||
for cveID, vuln := range r.ScannedCves {
|
||||
if _, ok := vuln.CveContents[models.RedHatAPI]; ok {
|
||||
@@ -46,128 +85,68 @@ func (red RedHat) fillFixed(driver db.DB, r *models.ScanResult) error {
|
||||
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
|
||||
red.setFixedCveToScanResult(&redCve, r)
|
||||
}
|
||||
} else {
|
||||
if driver == nil {
|
||||
return nil
|
||||
}
|
||||
for cveID, redCve := range driver.GetRedhatMulti(cveIDs) {
|
||||
for _, redCve := range driver.GetRedhatMulti(cveIDs) {
|
||||
if len(redCve.Name) == 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
|
||||
red.setFixedCveToScanResult(&redCve, r)
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (red RedHat) fillUnfixed(driver db.DB, r *models.ScanResult, ignoreWillNotFix bool) (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
|
||||
}
|
||||
}
|
||||
func (red RedHat) setFixedCveToScanResult(cve *gostmodels.RedhatCVE, r *models.ScanResult) {
|
||||
cveCont, mitigations := red.ConvertToModel(cve)
|
||||
v, ok := r.ScannedCves[cveCont.CveID]
|
||||
if ok {
|
||||
if v.CveContents == nil {
|
||||
v.CveContents = models.NewCveContents(*cveCont)
|
||||
} else {
|
||||
v.CveContents[models.RedHatAPI] = *cveCont
|
||||
}
|
||||
} else {
|
||||
if driver == nil {
|
||||
return 0, nil
|
||||
}
|
||||
for _, pack := range r.Packages {
|
||||
// CVE-ID: RedhatCVE
|
||||
cves := driver.GetUnfixedCvesRedhat(major(r.Release), pack.Name, ignoreWillNotFix)
|
||||
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
|
||||
}
|
||||
}
|
||||
v = models.VulnInfo{
|
||||
CveID: cveCont.CveID,
|
||||
CveContents: models.NewCveContents(*cveCont),
|
||||
Confidences: models.Confidences{models.RedHatAPIMatch},
|
||||
}
|
||||
}
|
||||
return nCVEs, nil
|
||||
v.Mitigations = append(v.Mitigations, mitigations...)
|
||||
r.ScannedCves[cveCont.CveID] = v
|
||||
}
|
||||
|
||||
func (red RedHat) setUnfixedCveToScanResult(cve *gostmodels.RedhatCVE, r *models.ScanResult) (newly bool) {
|
||||
cveCont, mitigations := 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},
|
||||
}
|
||||
newly = true
|
||||
}
|
||||
v.Mitigations = append(v.Mitigations, mitigations...)
|
||||
pkgStats := red.mergePackageStates(v,
|
||||
cve.PackageState, r.Packages, r.Release)
|
||||
if 0 < len(pkgStats) {
|
||||
v.AffectedPackages = pkgStats
|
||||
r.ScannedCves[cve.Name] = v
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
func (red RedHat) mergePackageStates(v models.VulnInfo, ps []gostmodels.RedhatPackageState, installed models.Packages, release string) (pkgStats models.PackageFixStatuses) {
|
||||
@@ -218,7 +197,7 @@ func (red RedHat) parseCwe(str string) (cwes []string) {
|
||||
}
|
||||
|
||||
// ConvertToModel converts gost model to vuls model
|
||||
func (red RedHat) ConvertToModel(cve *gostmodels.RedhatCVE) *models.CveContent {
|
||||
func (red RedHat) ConvertToModel(cve *gostmodels.RedhatCVE) (*models.CveContent, []models.Mitigation) {
|
||||
cwes := red.parseCwe(cve.Cwe)
|
||||
|
||||
details := []string{}
|
||||
@@ -249,6 +228,18 @@ func (red RedHat) ConvertToModel(cve *gostmodels.RedhatCVE) *models.CveContent {
|
||||
refs = append(refs, models.Reference{Link: r.Reference})
|
||||
}
|
||||
|
||||
vendorURL := "https://access.redhat.com/security/cve/" + cve.Name
|
||||
mitigations := []models.Mitigation{}
|
||||
if cve.Mitigation != "" {
|
||||
mitigations = []models.Mitigation{
|
||||
{
|
||||
CveContentType: models.RedHatAPI,
|
||||
Mitigation: cve.Mitigation,
|
||||
URL: vendorURL,
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
return &models.CveContent{
|
||||
Type: models.RedHatAPI,
|
||||
CveID: cve.Name,
|
||||
@@ -262,8 +253,7 @@ func (red RedHat) ConvertToModel(cve *gostmodels.RedhatCVE) *models.CveContent {
|
||||
Cvss3Severity: v3severity,
|
||||
References: refs,
|
||||
CweIDs: cwes,
|
||||
Mitigation: cve.Mitigation,
|
||||
Published: cve.PublicDate,
|
||||
SourceLink: "https://access.redhat.com/security/cve/" + cve.Name,
|
||||
}
|
||||
SourceLink: vendorURL,
|
||||
}, mitigations
|
||||
}
|
||||
|
||||
@@ -2,6 +2,7 @@ package gost
|
||||
|
||||
import (
|
||||
"net/http"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"github.com/cenkalti/backoff"
|
||||
@@ -159,7 +160,7 @@ func httpGet(url string, req request, resChan chan<- response, errChan chan<- er
|
||||
if count == retryMax {
|
||||
return nil
|
||||
}
|
||||
return xerrors.Errorf("HTTP GET error, url: %s, resp: %v, err: %w", url, resp, errs)
|
||||
return xerrors.Errorf("HTTP GET error, url: %s, resp: %v, err: %s", url, resp, errs)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
@@ -181,3 +182,7 @@ func httpGet(url string, req request, resChan chan<- response, errChan chan<- er
|
||||
json: body,
|
||||
}
|
||||
}
|
||||
|
||||
func major(osVer string) (majorVersion string) {
|
||||
return strings.Split(osVer, ".")[0]
|
||||
}
|
||||
|
||||
@@ -18,7 +18,8 @@ import (
|
||||
)
|
||||
|
||||
// DetectLibsCves fills LibraryScanner information
|
||||
func DetectLibsCves(r *models.ScanResult) (totalCnt int, err error) {
|
||||
func DetectLibsCves(r *models.ScanResult) (err error) {
|
||||
totalCnt := 0
|
||||
if len(r.LibraryScanners) == 0 {
|
||||
return
|
||||
}
|
||||
@@ -26,23 +27,23 @@ func DetectLibsCves(r *models.ScanResult) (totalCnt int, err error) {
|
||||
// initialize trivy's logger and db
|
||||
err = log.InitLogger(false, false)
|
||||
if err != nil {
|
||||
return 0, err
|
||||
return err
|
||||
}
|
||||
|
||||
util.Log.Info("Updating library db...")
|
||||
if err := downloadDB(config.Version, config.Conf.TrivyCacheDBDir, config.Conf.NoProgress, false, false); err != nil {
|
||||
return 0, err
|
||||
return err
|
||||
}
|
||||
|
||||
if err := db2.Init(config.Conf.TrivyCacheDBDir); err != nil {
|
||||
return 0, err
|
||||
return err
|
||||
}
|
||||
defer db2.Close()
|
||||
|
||||
for _, lib := range r.LibraryScanners {
|
||||
vinfos, err := lib.Scan()
|
||||
if err != nil {
|
||||
return 0, err
|
||||
return err
|
||||
}
|
||||
for _, vinfo := range vinfos {
|
||||
vinfo.Confidences.AppendIfMissing(models.TrivyMatch)
|
||||
@@ -56,7 +57,10 @@ func DetectLibsCves(r *models.ScanResult) (totalCnt int, err error) {
|
||||
totalCnt += len(vinfos)
|
||||
}
|
||||
|
||||
return totalCnt, nil
|
||||
util.Log.Infof("%s: %d CVEs are detected with Library",
|
||||
r.FormatServerName(), totalCnt)
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func downloadDB(appVersion, cacheDir string, quiet, light, skipUpdate bool) error {
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
package models
|
||||
|
||||
import (
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"github.com/aquasecurity/trivy-db/pkg/vulnsrc/vulnerability"
|
||||
@@ -42,15 +43,23 @@ func (v CveContents) Except(exceptCtypes ...CveContentType) (values CveContents)
|
||||
return
|
||||
}
|
||||
|
||||
// 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})
|
||||
// PrimarySrcURLs returns link of source
|
||||
func (v CveContents) PrimarySrcURLs(lang, myFamily, cveID string) (values []CveContentStr) {
|
||||
if cveID == "" {
|
||||
return
|
||||
}
|
||||
|
||||
if cont, found := v[Nvd]; found {
|
||||
for _, r := range cont.References {
|
||||
for _, t := range r.Tags {
|
||||
if t == "Vendor Advisory" {
|
||||
values = append(values, CveContentStr{Nvd, r.Link})
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
order := CveContentTypes{Nvd, NvdXML, NewCveContentType(myFamily)}
|
||||
order := CveContentTypes{Nvd, NewCveContentType(myFamily), GitHub}
|
||||
for _, ctype := range order {
|
||||
if cont, found := v[ctype]; found {
|
||||
if cont.SourceLink == "" {
|
||||
@@ -60,7 +69,13 @@ func (v CveContents) SourceLinks(lang, myFamily, cveID string) (values []CveCont
|
||||
}
|
||||
}
|
||||
|
||||
if len(values) == 0 {
|
||||
if lang == "ja" {
|
||||
if cont, found := v[Jvn]; found && 0 < len(cont.SourceLink) {
|
||||
values = append(values, CveContentStr{Jvn, cont.SourceLink})
|
||||
}
|
||||
}
|
||||
|
||||
if len(values) == 0 && strings.HasPrefix(cveID, "CVE") {
|
||||
return []CveContentStr{{
|
||||
Type: Nvd,
|
||||
Value: "https://nvd.nist.gov/vuln/detail/" + cveID,
|
||||
@@ -69,6 +84,22 @@ func (v CveContents) SourceLinks(lang, myFamily, cveID string) (values []CveCont
|
||||
return values
|
||||
}
|
||||
|
||||
// PatchURLs returns link of patch
|
||||
func (v CveContents) PatchURLs() (urls []string) {
|
||||
cont, found := v[Nvd]
|
||||
if !found {
|
||||
return
|
||||
}
|
||||
for _, r := range cont.References {
|
||||
for _, t := range r.Tags {
|
||||
if t == "Patch" {
|
||||
urls = append(urls, r.Link)
|
||||
}
|
||||
}
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
/*
|
||||
// Severities returns Severities
|
||||
func (v CveContents) Severities(myFamily string) (values []CveContentStr) {
|
||||
@@ -184,7 +215,6 @@ type CveContent struct {
|
||||
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"`
|
||||
}
|
||||
|
||||
@@ -199,8 +229,6 @@ type CveContentType string
|
||||
// NewCveContentType create CveContentType
|
||||
func NewCveContentType(name string) CveContentType {
|
||||
switch name {
|
||||
case "nvdxml":
|
||||
return NvdXML
|
||||
case "nvd":
|
||||
return Nvd
|
||||
case "jvn":
|
||||
@@ -220,31 +248,20 @@ func NewCveContentType(name string) CveContentType {
|
||||
case "microsoft":
|
||||
return Microsoft
|
||||
case "wordpress":
|
||||
return WPVulnDB
|
||||
return WpScan
|
||||
case "amazon":
|
||||
return Amazon
|
||||
case "trivy":
|
||||
return Trivy
|
||||
// case vulnerability.NodejsSecurityWg:
|
||||
// return NodeSec
|
||||
// case vulnerability.PythonSafetyDB:
|
||||
// return PythonSec
|
||||
// case vulnerability.RustSec:
|
||||
// return RustSec
|
||||
// case vulnerability.PhpSecurityAdvisories:
|
||||
// return PhpSec
|
||||
// case vulnerability.RubySec:
|
||||
// return RubySec
|
||||
case "GitHub":
|
||||
return Trivy
|
||||
default:
|
||||
return Unknown
|
||||
}
|
||||
}
|
||||
|
||||
const (
|
||||
// NvdXML is NvdXML
|
||||
NvdXML CveContentType = "nvdxml"
|
||||
|
||||
// Nvd is Nvd
|
||||
// Nvd is Nvd JSON
|
||||
Nvd CveContentType = "nvd"
|
||||
|
||||
// Jvn is Jvn
|
||||
@@ -277,26 +294,14 @@ const (
|
||||
// Microsoft is Microsoft
|
||||
Microsoft CveContentType = "microsoft"
|
||||
|
||||
// WPVulnDB is WordPress
|
||||
WPVulnDB CveContentType = "wpvulndb"
|
||||
// WpScan is WordPress
|
||||
WpScan CveContentType = "wpscan"
|
||||
|
||||
// Trivy is Trivy
|
||||
Trivy CveContentType = "trivy"
|
||||
|
||||
// NodeSec : for JS
|
||||
// NodeSec CveContentType = "node"
|
||||
|
||||
// // PythonSec : for PHP
|
||||
// PythonSec CveContentType = "python"
|
||||
|
||||
// // PhpSec : for PHP
|
||||
// PhpSec CveContentType = "php"
|
||||
|
||||
// // RubySec : for Ruby
|
||||
// RubySec CveContentType = "ruby"
|
||||
|
||||
// // RustSec : for Rust
|
||||
// RustSec CveContentType = "rust"
|
||||
// GitHub is GitHub Security Alerts
|
||||
GitHub CveContentType = "github"
|
||||
|
||||
// Unknown is Unknown
|
||||
Unknown CveContentType = "unknown"
|
||||
@@ -308,7 +313,6 @@ type CveContentTypes []CveContentType
|
||||
// AllCveContetTypes has all of CveContentTypes
|
||||
var AllCveContetTypes = CveContentTypes{
|
||||
Nvd,
|
||||
NvdXML,
|
||||
Jvn,
|
||||
RedHat,
|
||||
RedHatAPI,
|
||||
@@ -317,13 +321,9 @@ var AllCveContetTypes = CveContentTypes{
|
||||
Amazon,
|
||||
SUSE,
|
||||
DebianSecurityTracker,
|
||||
WPVulnDB,
|
||||
WpScan,
|
||||
Trivy,
|
||||
// NodeSec,
|
||||
// PythonSec,
|
||||
// PhpSec,
|
||||
// RubySec,
|
||||
// RustSec,
|
||||
GitHub,
|
||||
}
|
||||
|
||||
// Except returns CveContentTypes except for given args
|
||||
@@ -354,7 +354,8 @@ type References []Reference
|
||||
|
||||
// Reference has a related link of the CVE
|
||||
type Reference struct {
|
||||
Source string `json:"source"`
|
||||
Link string `json:"link"`
|
||||
RefID string `json:"refID"`
|
||||
Link string `json:"link,omitempty"`
|
||||
Source string `json:"source,omitempty"`
|
||||
RefID string `json:"refID,omitempty"`
|
||||
Tags []string `json:"tags,omitempty"`
|
||||
}
|
||||
|
||||
@@ -52,25 +52,43 @@ func TestSourceLinks(t *testing.T) {
|
||||
Type: RedHat,
|
||||
SourceLink: "https://access.redhat.com/security/cve/CVE-2017-6074",
|
||||
},
|
||||
NvdXML: {
|
||||
Type: NvdXML,
|
||||
Nvd: {
|
||||
Type: Nvd,
|
||||
References: []Reference{
|
||||
{
|
||||
Link: "https://lists.apache.org/thread.html/765be3606d865de513f6df9288842c3cf58b09a987c617a535f2b99d@%3Cusers.tapestry.apache.org%3E",
|
||||
Source: "",
|
||||
RefID: "",
|
||||
Tags: []string{"Vendor Advisory"},
|
||||
},
|
||||
{
|
||||
Link: "http://yahoo.com",
|
||||
Source: "",
|
||||
RefID: "",
|
||||
Tags: []string{"Vendor"},
|
||||
},
|
||||
},
|
||||
SourceLink: "https://nvd.nist.gov/vuln/detail/CVE-2017-6074",
|
||||
},
|
||||
},
|
||||
},
|
||||
out: []CveContentStr{
|
||||
{
|
||||
Type: Jvn,
|
||||
Value: "https://jvn.jp/vu/JVNVU93610402/",
|
||||
Type: Nvd,
|
||||
Value: "https://lists.apache.org/thread.html/765be3606d865de513f6df9288842c3cf58b09a987c617a535f2b99d@%3Cusers.tapestry.apache.org%3E",
|
||||
},
|
||||
{
|
||||
Type: NvdXML,
|
||||
Type: Nvd,
|
||||
Value: "https://nvd.nist.gov/vuln/detail/CVE-2017-6074",
|
||||
},
|
||||
{
|
||||
Type: RedHat,
|
||||
Value: "https://access.redhat.com/security/cve/CVE-2017-6074",
|
||||
},
|
||||
{
|
||||
Type: Jvn,
|
||||
Value: "https://jvn.jp/vu/JVNVU93610402/",
|
||||
},
|
||||
},
|
||||
},
|
||||
// lang: en
|
||||
@@ -87,17 +105,9 @@ func TestSourceLinks(t *testing.T) {
|
||||
Type: RedHat,
|
||||
SourceLink: "https://access.redhat.com/security/cve/CVE-2017-6074",
|
||||
},
|
||||
NvdXML: {
|
||||
Type: NvdXML,
|
||||
SourceLink: "https://nvd.nist.gov/vuln/detail/CVE-2017-6074",
|
||||
},
|
||||
},
|
||||
},
|
||||
out: []CveContentStr{
|
||||
{
|
||||
Type: NvdXML,
|
||||
Value: "https://nvd.nist.gov/vuln/detail/CVE-2017-6074",
|
||||
},
|
||||
{
|
||||
Type: RedHat,
|
||||
Value: "https://access.redhat.com/security/cve/CVE-2017-6074",
|
||||
@@ -120,71 +130,9 @@ func TestSourceLinks(t *testing.T) {
|
||||
},
|
||||
}
|
||||
for i, tt := range tests {
|
||||
actual := tt.in.cont.SourceLinks(tt.in.lang, "redhat", tt.in.cveID)
|
||||
actual := tt.in.cont.PrimarySrcURLs(tt.in.lang, "redhat", tt.in.cveID)
|
||||
if !reflect.DeepEqual(tt.out, actual) {
|
||||
t.Errorf("\n[%d] expected: %v\n actual: %v\n", i, tt.out, actual)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestVendorLink(t *testing.T) {
|
||||
type in struct {
|
||||
family string
|
||||
vinfo VulnInfo
|
||||
}
|
||||
var tests = []struct {
|
||||
in in
|
||||
out map[string]string
|
||||
}{
|
||||
{
|
||||
in: in{
|
||||
family: "redhat",
|
||||
vinfo: VulnInfo{
|
||||
CveID: "CVE-2017-6074",
|
||||
CveContents: CveContents{
|
||||
Jvn: {
|
||||
Type: Jvn,
|
||||
SourceLink: "https://jvn.jp/vu/JVNVU93610402/",
|
||||
},
|
||||
RedHat: {
|
||||
Type: RedHat,
|
||||
SourceLink: "https://access.redhat.com/security/cve/CVE-2017-6074",
|
||||
},
|
||||
NvdXML: {
|
||||
Type: NvdXML,
|
||||
SourceLink: "https://nvd.nist.gov/vuln/detail/CVE-2017-6074",
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
out: map[string]string{
|
||||
"RHEL-CVE": "https://access.redhat.com/security/cve/CVE-2017-6074",
|
||||
},
|
||||
},
|
||||
{
|
||||
in: in{
|
||||
family: "ubuntu",
|
||||
vinfo: VulnInfo{
|
||||
CveID: "CVE-2017-6074",
|
||||
CveContents: CveContents{
|
||||
RedHat: {
|
||||
Type: Ubuntu,
|
||||
SourceLink: "https://access.redhat.com/security/cve/CVE-2017-6074",
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
out: map[string]string{
|
||||
"Ubuntu-CVE": "http://people.ubuntu.com/~ubuntu-security/cve/CVE-2017-6074",
|
||||
},
|
||||
},
|
||||
}
|
||||
for _, tt := range tests {
|
||||
actual := tt.in.vinfo.VendorLinks(tt.in.family)
|
||||
for k := range tt.out {
|
||||
if tt.out[k] != actual[k] {
|
||||
t.Errorf("\nexpected: %s\n actual: %s\n", tt.out[k], actual[k])
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -3,7 +3,6 @@ package models
|
||||
import (
|
||||
"path/filepath"
|
||||
|
||||
"github.com/Masterminds/semver/v3"
|
||||
"github.com/aquasecurity/trivy-db/pkg/db"
|
||||
trivyDBTypes "github.com/aquasecurity/trivy-db/pkg/types"
|
||||
"github.com/aquasecurity/trivy/pkg/detector/library"
|
||||
@@ -31,6 +30,14 @@ func (lss LibraryScanners) Find(path, name string) map[string]types.Library {
|
||||
return filtered
|
||||
}
|
||||
|
||||
// Total returns total count of pkgs
|
||||
func (lss LibraryScanners) Total() (total int) {
|
||||
for _, lib := range lss {
|
||||
total += len(lib.Libs)
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
// LibraryScanner has libraries information
|
||||
type LibraryScanner struct {
|
||||
Path string
|
||||
@@ -45,13 +52,7 @@ func (s LibraryScanner) Scan() ([]VulnInfo, error) {
|
||||
}
|
||||
var vulnerabilities = []VulnInfo{}
|
||||
for _, pkg := range s.Libs {
|
||||
v, err := semver.StrictNewVersion(pkg.Version)
|
||||
if err != nil {
|
||||
util.Log.Debugf("new version cant detected %s@%s", pkg.Name, pkg.Version)
|
||||
continue
|
||||
}
|
||||
|
||||
tvulns, err := scanner.Detect(pkg.Name, v)
|
||||
tvulns, err := scanner.Detect(pkg.Name, pkg.Version)
|
||||
if err != nil {
|
||||
return nil, xerrors.Errorf("failed to detect %s vulnerabilities: %w", scanner.Type(), err)
|
||||
}
|
||||
|
||||
@@ -81,7 +81,7 @@ type Package struct {
|
||||
NewRelease string `json:"newRelease"`
|
||||
Arch string `json:"arch"`
|
||||
Repository string `json:"repository"`
|
||||
Changelog Changelog `json:"changelog"`
|
||||
Changelog *Changelog `json:"changelog,omitempty"`
|
||||
AffectedProcs []AffectedProcess `json:",omitempty"`
|
||||
NeedRestartProcs []NeedRestartProcess `json:",omitempty"`
|
||||
}
|
||||
@@ -174,28 +174,43 @@ type Changelog struct {
|
||||
|
||||
// AffectedProcess keep a processes information affected by software update
|
||||
type AffectedProcess struct {
|
||||
PID string `json:"pid,omitempty"`
|
||||
Name string `json:"name,omitempty"`
|
||||
ListenPorts []ListenPort `json:"listenPorts,omitempty"`
|
||||
PID string `json:"pid,omitempty"`
|
||||
Name string `json:"name,omitempty"`
|
||||
ListenPorts []string `json:"listenPorts,omitempty"`
|
||||
ListenPortStats []PortStat `json:"listenPortStats,omitempty"`
|
||||
}
|
||||
|
||||
// ListenPort has the result of parsing the port information to the address and port.
|
||||
type ListenPort struct {
|
||||
Address string `json:"address"`
|
||||
Port string `json:"port"`
|
||||
PortScanSuccessOn []string `json:"portScanSuccessOn"`
|
||||
// PortStat has the result of parsing the port information to the address and port.
|
||||
type PortStat struct {
|
||||
BindAddress string `json:"bindAddress"`
|
||||
Port string `json:"port"`
|
||||
PortReachableTo []string `json:"portReachableTo"`
|
||||
}
|
||||
|
||||
// HasPortScanSuccessOn checks if Package.AffectedProcs has PortScanSuccessOn
|
||||
func (p Package) HasPortScanSuccessOn() bool {
|
||||
// NewPortStat create a PortStat from ipPort str
|
||||
func NewPortStat(ipPort string) (*PortStat, error) {
|
||||
if ipPort == "" {
|
||||
return &PortStat{}, nil
|
||||
}
|
||||
sep := strings.LastIndex(ipPort, ":")
|
||||
if sep == -1 {
|
||||
return nil, xerrors.Errorf("Failed to parse IP:Port: %s", ipPort)
|
||||
}
|
||||
return &PortStat{
|
||||
BindAddress: ipPort[:sep],
|
||||
Port: ipPort[sep+1:],
|
||||
}, nil
|
||||
}
|
||||
|
||||
// HasReachablePort checks if Package.AffectedProcs has PortReachableTo
|
||||
func (p Package) HasReachablePort() bool {
|
||||
for _, ap := range p.AffectedProcs {
|
||||
for _, lp := range ap.ListenPorts {
|
||||
if len(lp.PortScanSuccessOn) > 0 {
|
||||
for _, lp := range ap.ListenPortStats {
|
||||
if len(lp.PortReachableTo) > 0 {
|
||||
return true
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return false
|
||||
}
|
||||
|
||||
|
||||
@@ -287,7 +287,7 @@ func TestPackage_FormatVersionFromTo(t *testing.T) {
|
||||
NewRelease: tt.fields.NewRelease,
|
||||
Arch: tt.fields.Arch,
|
||||
Repository: tt.fields.Repository,
|
||||
Changelog: tt.fields.Changelog,
|
||||
Changelog: &tt.fields.Changelog,
|
||||
AffectedProcs: tt.fields.AffectedProcs,
|
||||
NeedRestartProcs: tt.fields.NeedRestartProcs,
|
||||
}
|
||||
@@ -381,3 +381,50 @@ func Test_IsRaspbianPackage(t *testing.T) {
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func Test_parseListenPorts(t *testing.T) {
|
||||
tests := []struct {
|
||||
name string
|
||||
args string
|
||||
expect PortStat
|
||||
}{{
|
||||
name: "empty",
|
||||
args: "",
|
||||
expect: PortStat{
|
||||
BindAddress: "",
|
||||
Port: "",
|
||||
},
|
||||
}, {
|
||||
name: "normal",
|
||||
args: "127.0.0.1:22",
|
||||
expect: PortStat{
|
||||
BindAddress: "127.0.0.1",
|
||||
Port: "22",
|
||||
},
|
||||
}, {
|
||||
name: "asterisk",
|
||||
args: "*:22",
|
||||
expect: PortStat{
|
||||
BindAddress: "*",
|
||||
Port: "22",
|
||||
},
|
||||
}, {
|
||||
name: "ipv6_loopback",
|
||||
args: "[::1]:22",
|
||||
expect: PortStat{
|
||||
BindAddress: "[::1]",
|
||||
Port: "22",
|
||||
},
|
||||
}}
|
||||
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
listenPort, err := NewPortStat(tt.args)
|
||||
if err != nil {
|
||||
t.Errorf("unexpected error occurred: %s", err)
|
||||
} else if !reflect.DeepEqual(*listenPort, tt.expect) {
|
||||
t.Errorf("base.parseListenPorts() = %v, want %v", *listenPort, tt.expect)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
@@ -48,7 +48,8 @@ type ScanResult struct {
|
||||
RunningKernel Kernel `json:"runningKernel"`
|
||||
Packages Packages `json:"packages"`
|
||||
SrcPackages SrcPackages `json:",omitempty"`
|
||||
WordPressPackages *WordPressPackages `json:",omitempty"`
|
||||
EnabledDnfModules []string `json:"enabledDnfModules,omitempty"` // for dnf modules
|
||||
WordPressPackages WordPressPackages `json:",omitempty"`
|
||||
LibraryScanners LibraryScanners `json:"libraries,omitempty"`
|
||||
CweDict CweDict `json:"cweDict,omitempty"`
|
||||
Optional map[string]interface{} `json:",omitempty"`
|
||||
@@ -127,13 +128,7 @@ type Kernel struct {
|
||||
// FilterByCvssOver is filter function.
|
||||
func (r ScanResult) FilterByCvssOver(over float64) ScanResult {
|
||||
filtered := r.ScannedCves.Find(func(v VulnInfo) bool {
|
||||
v2Max := v.MaxCvss2Score()
|
||||
v3Max := v.MaxCvss3Score()
|
||||
max := v2Max.Value.Score
|
||||
if max < v3Max.Value.Score {
|
||||
max = v3Max.Value.Score
|
||||
}
|
||||
if over <= max {
|
||||
if over <= v.MaxCvssScore().Value.Score {
|
||||
return true
|
||||
}
|
||||
return false
|
||||
@@ -144,11 +139,12 @@ func (r ScanResult) FilterByCvssOver(over float64) ScanResult {
|
||||
|
||||
// FilterIgnoreCves is filter function.
|
||||
func (r ScanResult) FilterIgnoreCves() ScanResult {
|
||||
|
||||
ignoreCves := []string{}
|
||||
if len(r.Container.Name) == 0 {
|
||||
//TODO pass by args
|
||||
ignoreCves = config.Conf.Servers[r.ServerName].IgnoreCves
|
||||
} else {
|
||||
//TODO pass by args
|
||||
if s, ok := config.Conf.Servers[r.ServerName]; ok {
|
||||
if con, ok := s.Containers[r.Container.Name]; ok {
|
||||
ignoreCves = con.IgnoreCves
|
||||
@@ -175,8 +171,8 @@ func (r ScanResult) FilterIgnoreCves() ScanResult {
|
||||
}
|
||||
|
||||
// FilterUnfixed is filter function.
|
||||
func (r ScanResult) FilterUnfixed() ScanResult {
|
||||
if !config.Conf.IgnoreUnfixed {
|
||||
func (r ScanResult) FilterUnfixed(ignoreUnfixed bool) ScanResult {
|
||||
if !ignoreUnfixed {
|
||||
return r
|
||||
}
|
||||
filtered := r.ScannedCves.Find(func(v VulnInfo) bool {
|
||||
@@ -198,6 +194,7 @@ func (r ScanResult) FilterUnfixed() ScanResult {
|
||||
func (r ScanResult) FilterIgnorePkgs() ScanResult {
|
||||
var ignorePkgsRegexps []string
|
||||
if len(r.Container.Name) == 0 {
|
||||
//TODO pass by args
|
||||
ignorePkgsRegexps = config.Conf.Servers[r.ServerName].IgnorePkgsRegexp
|
||||
} else {
|
||||
if s, ok := config.Conf.Servers[r.ServerName]; ok {
|
||||
@@ -250,8 +247,8 @@ func (r ScanResult) FilterIgnorePkgs() ScanResult {
|
||||
}
|
||||
|
||||
// FilterInactiveWordPressLibs is filter function.
|
||||
func (r ScanResult) FilterInactiveWordPressLibs() ScanResult {
|
||||
if !config.Conf.Servers[r.ServerName].WordPress.IgnoreInactive {
|
||||
func (r ScanResult) FilterInactiveWordPressLibs(detectInactive bool) ScanResult {
|
||||
if detectInactive {
|
||||
return r
|
||||
}
|
||||
|
||||
@@ -347,16 +344,23 @@ func (r ScanResult) FormatTextReportHeader() string {
|
||||
buf.WriteString("=")
|
||||
}
|
||||
|
||||
return fmt.Sprintf("%s\n%s\n%s, %s, %s, %s, %s, %s\n",
|
||||
pkgs := r.FormatUpdatablePacksSummary()
|
||||
if 0 < len(r.WordPressPackages) {
|
||||
pkgs = fmt.Sprintf("%s, %d WordPress pkgs", pkgs, len(r.WordPressPackages))
|
||||
}
|
||||
if 0 < len(r.LibraryScanners) {
|
||||
pkgs = fmt.Sprintf("%s, %d libs", pkgs, r.LibraryScanners.Total())
|
||||
}
|
||||
|
||||
return fmt.Sprintf("%s\n%s\n%s, %s, %s, %s, %s\n%s\n",
|
||||
r.ServerInfo(),
|
||||
buf.String(),
|
||||
r.ScannedCves.FormatCveSummary(),
|
||||
r.ScannedCves.FormatFixedStatus(r.Packages),
|
||||
r.FormatUpdatablePacksSummary(),
|
||||
r.FormatExploitCveSummary(),
|
||||
r.FormatMetasploitCveSummary(),
|
||||
r.FormatAlertSummary(),
|
||||
)
|
||||
pkgs)
|
||||
}
|
||||
|
||||
// FormatUpdatablePacksSummary returns a summary of updatable packages
|
||||
@@ -387,7 +391,7 @@ func (r ScanResult) FormatExploitCveSummary() string {
|
||||
nExploitCve++
|
||||
}
|
||||
}
|
||||
return fmt.Sprintf("%d exploits", nExploitCve)
|
||||
return fmt.Sprintf("%d poc", nExploitCve)
|
||||
}
|
||||
|
||||
// FormatMetasploitCveSummary returns a summary of exploit cve
|
||||
@@ -398,7 +402,7 @@ func (r ScanResult) FormatMetasploitCveSummary() string {
|
||||
nMetasploitCve++
|
||||
}
|
||||
}
|
||||
return fmt.Sprintf("%d modules", nMetasploitCve)
|
||||
return fmt.Sprintf("%d exploits", nMetasploitCve)
|
||||
}
|
||||
|
||||
// FormatAlertSummary returns a summary of CERT alerts
|
||||
@@ -422,6 +426,7 @@ func (r ScanResult) isDisplayUpdatableNum() bool {
|
||||
}
|
||||
|
||||
var mode config.ScanMode
|
||||
//TODO pass by args
|
||||
s, _ := config.Conf.Servers[r.ServerName]
|
||||
mode = s.Mode
|
||||
|
||||
@@ -454,10 +459,8 @@ func (r ScanResult) IsContainer() bool {
|
||||
// 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
|
||||
}
|
||||
if ok := s.Mode.IsDeep(); ok {
|
||||
return true
|
||||
}
|
||||
}
|
||||
return false
|
||||
@@ -505,6 +508,7 @@ func (r ScanResult) RemoveRaspbianPackFromResult() ScanResult {
|
||||
return result
|
||||
}
|
||||
|
||||
// ClearFields clears a given fields of ScanResult
|
||||
func (r ScanResult) ClearFields(targetTagNames []string) ScanResult {
|
||||
if len(targetTagNames) == 0 {
|
||||
return r
|
||||
|
||||
@@ -18,6 +18,7 @@ func TestFilterByCvssOver(t *testing.T) {
|
||||
in in
|
||||
out ScanResult
|
||||
}{
|
||||
//0
|
||||
{
|
||||
in: in{
|
||||
over: 7.0,
|
||||
@@ -27,7 +28,7 @@ func TestFilterByCvssOver(t *testing.T) {
|
||||
CveID: "CVE-2017-0001",
|
||||
CveContents: NewCveContents(
|
||||
CveContent{
|
||||
Type: NvdXML,
|
||||
Type: Nvd,
|
||||
CveID: "CVE-2017-0001",
|
||||
Cvss2Score: 7.1,
|
||||
LastModified: time.Time{},
|
||||
@@ -38,7 +39,7 @@ func TestFilterByCvssOver(t *testing.T) {
|
||||
CveID: "CVE-2017-0002",
|
||||
CveContents: NewCveContents(
|
||||
CveContent{
|
||||
Type: NvdXML,
|
||||
Type: Nvd,
|
||||
CveID: "CVE-2017-0002",
|
||||
Cvss2Score: 6.9,
|
||||
LastModified: time.Time{},
|
||||
@@ -49,7 +50,7 @@ func TestFilterByCvssOver(t *testing.T) {
|
||||
CveID: "CVE-2017-0003",
|
||||
CveContents: NewCveContents(
|
||||
CveContent{
|
||||
Type: NvdXML,
|
||||
Type: Nvd,
|
||||
CveID: "CVE-2017-0003",
|
||||
Cvss2Score: 6.9,
|
||||
LastModified: time.Time{},
|
||||
@@ -71,7 +72,7 @@ func TestFilterByCvssOver(t *testing.T) {
|
||||
CveID: "CVE-2017-0001",
|
||||
CveContents: NewCveContents(
|
||||
CveContent{
|
||||
Type: NvdXML,
|
||||
Type: Nvd,
|
||||
CveID: "CVE-2017-0001",
|
||||
Cvss2Score: 7.1,
|
||||
LastModified: time.Time{},
|
||||
@@ -82,7 +83,7 @@ func TestFilterByCvssOver(t *testing.T) {
|
||||
CveID: "CVE-2017-0003",
|
||||
CveContents: NewCveContents(
|
||||
CveContent{
|
||||
Type: NvdXML,
|
||||
Type: Nvd,
|
||||
CveID: "CVE-2017-0003",
|
||||
Cvss2Score: 6.9,
|
||||
LastModified: time.Time{},
|
||||
@@ -98,7 +99,7 @@ func TestFilterByCvssOver(t *testing.T) {
|
||||
},
|
||||
},
|
||||
},
|
||||
// OVAL Severity
|
||||
//1 OVAL Severity
|
||||
{
|
||||
in: in{
|
||||
over: 7.0,
|
||||
@@ -110,7 +111,7 @@ func TestFilterByCvssOver(t *testing.T) {
|
||||
CveContent{
|
||||
Type: Ubuntu,
|
||||
CveID: "CVE-2017-0001",
|
||||
Cvss2Severity: "HIGH",
|
||||
Cvss3Severity: "HIGH",
|
||||
LastModified: time.Time{},
|
||||
},
|
||||
),
|
||||
@@ -119,9 +120,9 @@ func TestFilterByCvssOver(t *testing.T) {
|
||||
CveID: "CVE-2017-0002",
|
||||
CveContents: NewCveContents(
|
||||
CveContent{
|
||||
Type: RedHat,
|
||||
Type: Debian,
|
||||
CveID: "CVE-2017-0002",
|
||||
Cvss2Severity: "CRITICAL",
|
||||
Cvss3Severity: "CRITICAL",
|
||||
LastModified: time.Time{},
|
||||
},
|
||||
),
|
||||
@@ -130,9 +131,9 @@ func TestFilterByCvssOver(t *testing.T) {
|
||||
CveID: "CVE-2017-0003",
|
||||
CveContents: NewCveContents(
|
||||
CveContent{
|
||||
Type: Oracle,
|
||||
Type: GitHub,
|
||||
CveID: "CVE-2017-0003",
|
||||
Cvss2Severity: "IMPORTANT",
|
||||
Cvss3Severity: "IMPORTANT",
|
||||
LastModified: time.Time{},
|
||||
},
|
||||
),
|
||||
@@ -148,7 +149,7 @@ func TestFilterByCvssOver(t *testing.T) {
|
||||
CveContent{
|
||||
Type: Ubuntu,
|
||||
CveID: "CVE-2017-0001",
|
||||
Cvss2Severity: "HIGH",
|
||||
Cvss3Severity: "HIGH",
|
||||
LastModified: time.Time{},
|
||||
},
|
||||
),
|
||||
@@ -157,9 +158,9 @@ func TestFilterByCvssOver(t *testing.T) {
|
||||
CveID: "CVE-2017-0002",
|
||||
CveContents: NewCveContents(
|
||||
CveContent{
|
||||
Type: RedHat,
|
||||
Type: Debian,
|
||||
CveID: "CVE-2017-0002",
|
||||
Cvss2Severity: "CRITICAL",
|
||||
Cvss3Severity: "CRITICAL",
|
||||
LastModified: time.Time{},
|
||||
},
|
||||
),
|
||||
@@ -168,9 +169,9 @@ func TestFilterByCvssOver(t *testing.T) {
|
||||
CveID: "CVE-2017-0003",
|
||||
CveContents: NewCveContents(
|
||||
CveContent{
|
||||
Type: Oracle,
|
||||
Type: GitHub,
|
||||
CveID: "CVE-2017-0003",
|
||||
Cvss2Severity: "IMPORTANT",
|
||||
Cvss3Severity: "IMPORTANT",
|
||||
LastModified: time.Time{},
|
||||
},
|
||||
),
|
||||
@@ -179,13 +180,14 @@ func TestFilterByCvssOver(t *testing.T) {
|
||||
},
|
||||
},
|
||||
}
|
||||
for _, tt := range tests {
|
||||
pp.ColoringEnabled = false
|
||||
for i, tt := range tests {
|
||||
actual := tt.in.rs.FilterByCvssOver(tt.in.over)
|
||||
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)
|
||||
t.Errorf("[%d: %s] expected: %v\n actual: %v\n", i, k, o, a)
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -392,8 +394,7 @@ func TestFilterUnfixed(t *testing.T) {
|
||||
},
|
||||
}
|
||||
for i, tt := range tests {
|
||||
config.Conf.IgnoreUnfixed = true
|
||||
actual := tt.in.FilterUnfixed()
|
||||
actual := tt.in.FilterUnfixed(true)
|
||||
if !reflect.DeepEqual(tt.out.ScannedCves, actual.ScannedCves) {
|
||||
o := pp.Sprintf("%v", tt.out.ScannedCves)
|
||||
a := pp.Sprintf("%v", actual.ScannedCves)
|
||||
|
||||
@@ -1,3 +1,5 @@
|
||||
// +build !scanner
|
||||
|
||||
package models
|
||||
|
||||
import (
|
||||
@@ -47,9 +49,9 @@ func ConvertJvnToModel(cveID string, jvn *cvedict.Jvn) *CveContent {
|
||||
}
|
||||
|
||||
// ConvertNvdJSONToModel convert NVD to CveContent
|
||||
func ConvertNvdJSONToModel(cveID string, nvd *cvedict.NvdJSON) *CveContent {
|
||||
func ConvertNvdJSONToModel(cveID string, nvd *cvedict.NvdJSON) (*CveContent, []Exploit, []Mitigation) {
|
||||
if nvd == nil {
|
||||
return nil
|
||||
return nil, nil, nil
|
||||
}
|
||||
// var cpes = []Cpe{}
|
||||
// for _, c := range nvd.Cpes {
|
||||
@@ -59,12 +61,33 @@ func ConvertNvdJSONToModel(cveID string, nvd *cvedict.NvdJSON) *CveContent {
|
||||
// })
|
||||
// }
|
||||
|
||||
var refs = []Reference{}
|
||||
refs := []Reference{}
|
||||
exploits := []Exploit{}
|
||||
mitigations := []Mitigation{}
|
||||
for _, r := range nvd.References {
|
||||
var tags []string
|
||||
if 0 < len(r.Tags) {
|
||||
tags = strings.Split(r.Tags, ",")
|
||||
}
|
||||
refs = append(refs, Reference{
|
||||
Link: r.Link,
|
||||
Source: r.Source,
|
||||
Tags: tags,
|
||||
})
|
||||
if strings.Contains(r.Tags, "Exploit") {
|
||||
exploits = append(exploits, Exploit{
|
||||
//TODO Add const to here
|
||||
// https://github.com/vulsio/go-exploitdb/blob/master/models/exploit.go#L13-L18
|
||||
ExploitType: "nvd",
|
||||
URL: r.Link,
|
||||
})
|
||||
}
|
||||
if strings.Contains(r.Tags, "Mitigation") {
|
||||
mitigations = append(mitigations, Mitigation{
|
||||
CveContentType: Nvd,
|
||||
URL: r.Link,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
cweIDs := []string{}
|
||||
@@ -93,5 +116,5 @@ func ConvertNvdJSONToModel(cveID string, nvd *cvedict.NvdJSON) *CveContent {
|
||||
References: refs,
|
||||
Published: nvd.PublishedDate,
|
||||
LastModified: nvd.LastModifiedDate,
|
||||
}
|
||||
}, exploits, mitigations
|
||||
}
|
||||
|
||||
@@ -8,7 +8,7 @@ import (
|
||||
"time"
|
||||
|
||||
"github.com/future-architect/vuls/config"
|
||||
exploitmodels "github.com/mozqnet/go-exploitdb/models"
|
||||
exploitmodels "github.com/vulsio/go-exploitdb/models"
|
||||
)
|
||||
|
||||
// VulnInfos has a map of VulnInfo
|
||||
@@ -57,11 +57,13 @@ func (v VulnInfos) ToSortedSlice() (sorted []VulnInfo) {
|
||||
func (v VulnInfos) CountGroupBySeverity() map[string]int {
|
||||
m := map[string]int{}
|
||||
for _, vInfo := range v {
|
||||
score := vInfo.MaxCvss2Score().Value.Score
|
||||
score := vInfo.MaxCvss3Score().Value.Score
|
||||
if score < 0.1 {
|
||||
score = vInfo.MaxCvss3Score().Value.Score
|
||||
score = vInfo.MaxCvss2Score().Value.Score
|
||||
}
|
||||
switch {
|
||||
case 9 <= score:
|
||||
m["Critical"]++
|
||||
case 7.0 <= score:
|
||||
m["High"]++
|
||||
case 4.0 <= score:
|
||||
@@ -80,12 +82,12 @@ func (v VulnInfos) FormatCveSummary() string {
|
||||
m := v.CountGroupBySeverity()
|
||||
|
||||
if config.Conf.IgnoreUnscoredCves {
|
||||
return fmt.Sprintf("Total: %d (High:%d Medium:%d Low:%d)",
|
||||
m["High"]+m["Medium"]+m["Low"], m["High"], m["Medium"], m["Low"])
|
||||
return fmt.Sprintf("Total: %d (Critical:%d High:%d Medium:%d Low:%d)",
|
||||
m["High"]+m["Medium"]+m["Low"], m["Critical"], m["High"], m["Medium"], m["Low"])
|
||||
}
|
||||
return fmt.Sprintf("Total: %d (High:%d Medium:%d Low:%d ?:%d)",
|
||||
return fmt.Sprintf("Total: %d (Critical:%d High:%d Medium:%d Low:%d ?:%d)",
|
||||
m["High"]+m["Medium"]+m["Low"]+m["Unknown"],
|
||||
m["High"], m["Medium"], m["Low"], m["Unknown"])
|
||||
m["Critical"], m["High"], m["Medium"], m["Low"], m["Unknown"])
|
||||
}
|
||||
|
||||
// FormatFixedStatus summarize the number of cves are fixed.
|
||||
@@ -134,7 +136,7 @@ func (ps PackageFixStatuses) Sort() {
|
||||
return
|
||||
}
|
||||
|
||||
// PackageFixStatus has name and other status abount the package
|
||||
// PackageFixStatus has name and other status about the package
|
||||
type PackageFixStatus struct {
|
||||
Name string `json:"name,omitempty"`
|
||||
NotFixedYet bool `json:"notFixedYet,omitempty"`
|
||||
@@ -147,10 +149,11 @@ type VulnInfo struct {
|
||||
CveID string `json:"cveID,omitempty"`
|
||||
Confidences Confidences `json:"confidences,omitempty"`
|
||||
AffectedPackages PackageFixStatuses `json:"affectedPackages,omitempty"`
|
||||
DistroAdvisories DistroAdvisories `json:"distroAdvisories,omitempty"` // for Aamazon, RHEL, FreeBSD
|
||||
DistroAdvisories DistroAdvisories `json:"distroAdvisories,omitempty"` // for Amazon, RHEL, FreeBSD
|
||||
CveContents CveContents `json:"cveContents,omitempty"`
|
||||
Exploits []Exploit `json:"exploits,omitempty"`
|
||||
Metasploits []Metasploit `json:"metasploits,omitempty"`
|
||||
Mitigations []Mitigation `json:"mitigations,omitempty"`
|
||||
AlertDict AlertDict `json:"alertDict,omitempty"`
|
||||
CpeURIs []string `json:"cpeURIs,omitempty"` // CpeURIs related to this CVE defined in config.toml
|
||||
GitHubSecurityAlerts GitHubSecurityAlerts `json:"gitHubSecurityAlerts,omitempty"`
|
||||
@@ -160,7 +163,7 @@ type VulnInfo struct {
|
||||
VulnType string `json:"vulnType,omitempty"`
|
||||
}
|
||||
|
||||
// Alert has XCERT alert information
|
||||
// Alert has CERT alert information
|
||||
type Alert struct {
|
||||
URL string `json:"url,omitempty"`
|
||||
Title string `json:"title,omitempty"`
|
||||
@@ -233,24 +236,28 @@ func (g WpPackages) Add(pkg WpPackage) WpPackages {
|
||||
return append(g, pkg)
|
||||
}
|
||||
|
||||
// Titles returns tilte (TUI)
|
||||
// Titles returns title (TUI)
|
||||
func (v VulnInfo) Titles(lang, myFamily string) (values []CveContentStr) {
|
||||
if lang == "ja" {
|
||||
if cont, found := v.CveContents[Jvn]; found && 0 < len(cont.Title) {
|
||||
if cont, found := v.CveContents[Jvn]; found && cont.Title != "" {
|
||||
values = append(values, CveContentStr{Jvn, cont.Title})
|
||||
}
|
||||
}
|
||||
|
||||
// RedHat API has one line title.
|
||||
if cont, found := v.CveContents[RedHatAPI]; found && 0 < len(cont.Title) {
|
||||
if cont, found := v.CveContents[RedHatAPI]; found && cont.Title != "" {
|
||||
values = append(values, CveContentStr{RedHatAPI, cont.Title})
|
||||
}
|
||||
|
||||
order := CveContentTypes{Trivy, Nvd, NvdXML, NewCveContentType(myFamily)}
|
||||
// GitHub security alerts has a title.
|
||||
if cont, found := v.CveContents[GitHub]; found && cont.Title != "" {
|
||||
values = append(values, CveContentStr{GitHub, cont.Title})
|
||||
}
|
||||
|
||||
order := CveContentTypes{Trivy, Nvd, 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) {
|
||||
if cont, found := v.CveContents[ctype]; found && cont.Summary != "" {
|
||||
summary := strings.Replace(cont.Summary, "\n", " ", -1)
|
||||
values = append(values, CveContentStr{
|
||||
Type: ctype,
|
||||
@@ -278,7 +285,7 @@ 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 && cont.Summary != "" {
|
||||
summary := cont.Title
|
||||
summary += "\n" + strings.Replace(
|
||||
strings.Replace(cont.Summary, "\n", " ", -1), "\r", " ", -1)
|
||||
@@ -286,10 +293,10 @@ func (v VulnInfo) Summaries(lang, myFamily string) (values []CveContentStr) {
|
||||
}
|
||||
}
|
||||
|
||||
order := CveContentTypes{Trivy, NewCveContentType(myFamily), Nvd, NvdXML}
|
||||
order := CveContentTypes{Trivy, NewCveContentType(myFamily), Nvd, GitHub}
|
||||
order = append(order, AllCveContetTypes.Except(append(order, Jvn)...)...)
|
||||
for _, ctype := range order {
|
||||
if cont, found := v.CveContents[ctype]; found && 0 < len(cont.Summary) {
|
||||
if cont, found := v.CveContents[ctype]; found && cont.Summary != "" {
|
||||
summary := strings.Replace(cont.Summary, "\n", " ", -1)
|
||||
values = append(values, CveContentStr{
|
||||
Type: ctype,
|
||||
@@ -305,9 +312,9 @@ func (v VulnInfo) Summaries(lang, myFamily string) (values []CveContentStr) {
|
||||
})
|
||||
}
|
||||
|
||||
if v, ok := v.CveContents[WPVulnDB]; ok {
|
||||
if v, ok := v.CveContents[WpScan]; ok {
|
||||
values = append(values, CveContentStr{
|
||||
Type: "WPVDB",
|
||||
Type: WpScan,
|
||||
Value: v.Title,
|
||||
})
|
||||
}
|
||||
@@ -322,36 +329,12 @@ func (v VulnInfo) Summaries(lang, myFamily string) (values []CveContentStr) {
|
||||
return
|
||||
}
|
||||
|
||||
// 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 < 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, RedHatAPI, RedHat, Jvn}
|
||||
if myFamily != config.RedHat && myFamily != config.CentOS {
|
||||
order = append(order, NewCveContentType(myFamily))
|
||||
}
|
||||
func (v VulnInfo) Cvss2Scores() (values []CveContentCvss) {
|
||||
order := []CveContentType{RedHatAPI, RedHat, Nvd, Jvn}
|
||||
for _, ctype := range order {
|
||||
if cont, found := v.CveContents[ctype]; found {
|
||||
if cont.Cvss2Score == 0 || cont.Cvss2Severity == "" {
|
||||
if cont.Cvss2Score == 0 && cont.Cvss2Severity == "" {
|
||||
continue
|
||||
}
|
||||
// https://nvd.nist.gov/vuln-metrics/cvss
|
||||
@@ -366,52 +349,17 @@ func (v VulnInfo) Cvss2Scores(myFamily string) (values []CveContentCvss) {
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
for _, adv := range v.DistroAdvisories {
|
||||
if adv.Severity != "" {
|
||||
values = append(values, CveContentCvss{
|
||||
Type: "Advisory",
|
||||
Value: Cvss{
|
||||
Type: CVSS2,
|
||||
Score: severityToV2ScoreRoughly(adv.Severity),
|
||||
CalculatedBySeverity: true,
|
||||
Vector: "-",
|
||||
Severity: strings.ToUpper(adv.Severity),
|
||||
},
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
// 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) {
|
||||
order := []CveContentType{Nvd, RedHatAPI, RedHat, Jvn}
|
||||
order := []CveContentType{RedHatAPI, RedHat, Nvd, Jvn}
|
||||
for _, ctype := range order {
|
||||
if cont, found := v.CveContents[ctype]; found {
|
||||
if cont.Cvss3Score == 0 && cont.Cvss3Severity == "" {
|
||||
continue
|
||||
}
|
||||
// https://nvd.nist.gov/vuln-metrics/cvss
|
||||
values = append(values, CveContentCvss{
|
||||
Type: ctype,
|
||||
@@ -425,131 +373,74 @@ func (v VulnInfo) Cvss3Scores() (values []CveContentCvss) {
|
||||
}
|
||||
}
|
||||
|
||||
if cont, found := v.CveContents[Trivy]; found && cont.Cvss3Severity != "" {
|
||||
values = append(values, CveContentCvss{
|
||||
Type: Trivy,
|
||||
Value: Cvss{
|
||||
Type: CVSS3,
|
||||
Score: severityToV2ScoreRoughly(cont.Cvss3Severity),
|
||||
Severity: strings.ToUpper(cont.Cvss3Severity),
|
||||
},
|
||||
})
|
||||
}
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
// MaxCvss3Score returns Max CVSS V3 Score
|
||||
func (v VulnInfo) MaxCvss3Score() CveContentCvss {
|
||||
order := []CveContentType{Nvd, RedHat, RedHatAPI, Jvn}
|
||||
max := 0.0
|
||||
value := CveContentCvss{
|
||||
Type: Unknown,
|
||||
Value: Cvss{Type: CVSS3},
|
||||
}
|
||||
for _, ctype := range order {
|
||||
if cont, found := v.CveContents[ctype]; found && max < cont.Cvss3Score {
|
||||
// https://nvd.nist.gov/vuln-metrics/cvss
|
||||
value = CveContentCvss{
|
||||
for _, ctype := range []CveContentType{Debian, DebianSecurityTracker, Ubuntu, Amazon, Trivy, GitHub, WpScan} {
|
||||
if cont, found := v.CveContents[ctype]; found && cont.Cvss3Severity != "" {
|
||||
values = append(values, CveContentCvss{
|
||||
Type: ctype,
|
||||
Value: Cvss{
|
||||
Type: CVSS3,
|
||||
Score: cont.Cvss3Score,
|
||||
Vector: cont.Cvss3Vector,
|
||||
Severity: strings.ToUpper(cont.Cvss3Severity),
|
||||
Type: CVSS3,
|
||||
Score: severityToCvssScoreRoughly(cont.Cvss3Severity),
|
||||
CalculatedBySeverity: true,
|
||||
Severity: strings.ToUpper(cont.Cvss3Severity),
|
||||
},
|
||||
}
|
||||
max = cont.Cvss3Score
|
||||
})
|
||||
}
|
||||
}
|
||||
return value
|
||||
|
||||
// Memo: Only RedHat, Oracle and Amazon has severity data in advisory.
|
||||
for _, adv := range v.DistroAdvisories {
|
||||
if adv.Severity != "" {
|
||||
score := severityToCvssScoreRoughly(adv.Severity)
|
||||
values = append(values, CveContentCvss{
|
||||
Type: "Vendor",
|
||||
Value: Cvss{
|
||||
Type: CVSS3,
|
||||
Score: score,
|
||||
CalculatedBySeverity: true,
|
||||
Severity: strings.ToUpper(adv.Severity),
|
||||
},
|
||||
})
|
||||
}
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
// MaxCvssScore returns max CVSS Score
|
||||
// If there is no CVSS Score, return Severity as a numerical value.
|
||||
func (v VulnInfo) MaxCvssScore() CveContentCvss {
|
||||
v3Max := v.MaxCvss3Score()
|
||||
v2Max := v.MaxCvss2Score()
|
||||
max := v3Max
|
||||
if max.Type == Unknown {
|
||||
return v2Max
|
||||
if v3Max.Type != Unknown {
|
||||
return v3Max
|
||||
}
|
||||
return v.MaxCvss2Score()
|
||||
}
|
||||
|
||||
if max.Value.Score < v2Max.Value.Score && !v2Max.Value.CalculatedBySeverity {
|
||||
max = v2Max
|
||||
// MaxCvss3Score returns Max CVSS V3 Score
|
||||
func (v VulnInfo) MaxCvss3Score() CveContentCvss {
|
||||
max := CveContentCvss{
|
||||
Type: Unknown,
|
||||
Value: Cvss{Type: CVSS3},
|
||||
}
|
||||
for _, cvss := range v.Cvss3Scores() {
|
||||
if max.Value.Score < cvss.Value.Score {
|
||||
max = cvss
|
||||
}
|
||||
}
|
||||
return max
|
||||
}
|
||||
|
||||
// MaxCvss2Score returns Max CVSS V2 Score
|
||||
func (v VulnInfo) MaxCvss2Score() CveContentCvss {
|
||||
order := []CveContentType{Nvd, NvdXML, RedHat, RedHatAPI, Jvn}
|
||||
max := 0.0
|
||||
value := CveContentCvss{
|
||||
max := CveContentCvss{
|
||||
Type: Unknown,
|
||||
Value: Cvss{Type: CVSS2},
|
||||
}
|
||||
for _, ctype := range order {
|
||||
if cont, found := v.CveContents[ctype]; found && max < cont.Cvss2Score {
|
||||
// https://nvd.nist.gov/vuln-metrics/cvss
|
||||
value = CveContentCvss{
|
||||
Type: ctype,
|
||||
Value: Cvss{
|
||||
Type: CVSS2,
|
||||
Score: cont.Cvss2Score,
|
||||
Vector: cont.Cvss2Vector,
|
||||
Severity: strings.ToUpper(cont.Cvss2Severity),
|
||||
},
|
||||
}
|
||||
max = cont.Cvss2Score
|
||||
for _, cvss := range v.Cvss2Scores() {
|
||||
if max.Value.Score < cvss.Value.Score {
|
||||
max = cvss
|
||||
}
|
||||
}
|
||||
if 0 < max {
|
||||
return value
|
||||
}
|
||||
|
||||
// If CVSS score isn't on NVD, RedHat and JVN, use OVAL and advisory Severity.
|
||||
// Convert severity to cvss srore roughly, then returns max severity.
|
||||
// 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.Cvss2Severity) {
|
||||
score := severityToV2ScoreRoughly(cont.Cvss2Severity)
|
||||
if max < score {
|
||||
value = CveContentCvss{
|
||||
Type: ctype,
|
||||
Value: Cvss{
|
||||
Type: CVSS2,
|
||||
Score: score,
|
||||
CalculatedBySeverity: true,
|
||||
Vector: cont.Cvss2Vector,
|
||||
Severity: strings.ToUpper(cont.Cvss2Severity),
|
||||
},
|
||||
}
|
||||
}
|
||||
max = score
|
||||
}
|
||||
}
|
||||
|
||||
// Only RedHat, Oracle and Amazon has severity data in advisory.
|
||||
for _, adv := range v.DistroAdvisories {
|
||||
if adv.Severity != "" {
|
||||
score := severityToV2ScoreRoughly(adv.Severity)
|
||||
if max < score {
|
||||
value = CveContentCvss{
|
||||
Type: "Vendor",
|
||||
Value: Cvss{
|
||||
Type: CVSS2,
|
||||
Score: score,
|
||||
CalculatedBySeverity: true,
|
||||
Vector: "-",
|
||||
Severity: adv.Severity,
|
||||
},
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
return value
|
||||
return max
|
||||
}
|
||||
|
||||
// AttackVector returns attack vector string
|
||||
@@ -615,10 +506,10 @@ type CveContentCvss struct {
|
||||
type CvssType string
|
||||
|
||||
const (
|
||||
// CVSS2 means CVSS vesion2
|
||||
// CVSS2 means CVSS version2
|
||||
CVSS2 CvssType = "2"
|
||||
|
||||
// CVSS3 means CVSS vesion3
|
||||
// CVSS3 means CVSS version3
|
||||
CVSS3 CvssType = "3"
|
||||
)
|
||||
|
||||
@@ -633,16 +524,28 @@ type Cvss struct {
|
||||
|
||||
// Format CVSS Score and Vector
|
||||
func (c Cvss) Format() string {
|
||||
if c.Score == 0 || c.Vector == "" {
|
||||
return c.Severity
|
||||
if c.Vector == "" {
|
||||
return fmt.Sprintf("%s %s", c.SeverityToCvssScoreRange(), c.Severity)
|
||||
}
|
||||
switch c.Type {
|
||||
case CVSS2:
|
||||
return fmt.Sprintf("%3.1f/%s %s", c.Score, c.Vector, c.Severity)
|
||||
case CVSS3:
|
||||
return fmt.Sprintf("%3.1f/%s %s", c.Score, c.Vector, c.Severity)
|
||||
return fmt.Sprintf("%3.1f/%s %s", c.Score, c.Vector, c.Severity)
|
||||
}
|
||||
|
||||
func (c Cvss) SeverityToCvssScoreRange() string {
|
||||
return severityToCvssScoreRange(c.Severity)
|
||||
}
|
||||
|
||||
func severityToCvssScoreRange(severity string) string {
|
||||
switch strings.ToUpper(severity) {
|
||||
case "CRITICAL":
|
||||
return "9.0-10.0"
|
||||
case "IMPORTANT", "HIGH":
|
||||
return "7.0-8.9"
|
||||
case "MODERATE", "MEDIUM":
|
||||
return "4.0-6.9"
|
||||
case "LOW":
|
||||
return "0.1-3.9"
|
||||
}
|
||||
return ""
|
||||
return "None"
|
||||
}
|
||||
|
||||
// Amazon Linux Security Advisory
|
||||
@@ -657,7 +560,7 @@ func (c Cvss) Format() string {
|
||||
// Critical, High, Medium, Low
|
||||
// https://wiki.ubuntu.com/Bugs/Importance
|
||||
// https://people.canonical.com/~ubuntu-security/cve/priority.html
|
||||
func severityToV2ScoreRoughly(severity string) float64 {
|
||||
func severityToCvssScoreRoughly(severity string) float64 {
|
||||
switch strings.ToUpper(severity) {
|
||||
case "CRITICAL":
|
||||
return 10.0
|
||||
@@ -680,70 +583,6 @@ func (v VulnInfo) FormatMaxCvssScore() string {
|
||||
max.Type)
|
||||
}
|
||||
|
||||
// Cvss2CalcURL returns CVSS v2 caluclator's URL
|
||||
func (v VulnInfo) Cvss2CalcURL() string {
|
||||
return "https://nvd.nist.gov/vuln-metrics/cvss/v2-calculator?name=" + v.CveID
|
||||
}
|
||||
|
||||
// Cvss3CalcURL returns CVSS v3 caluclator's URL
|
||||
func (v VulnInfo) Cvss3CalcURL() string {
|
||||
return "https://nvd.nist.gov/vuln-metrics/cvss/v3-calculator?name=" + v.CveID
|
||||
}
|
||||
|
||||
// VendorLinks returns links of vendor support's URL
|
||||
func (v VulnInfo) VendorLinks(family string) map[string]string {
|
||||
links := map[string]string{}
|
||||
if strings.HasPrefix(v.CveID, "WPVDBID") {
|
||||
links["WPVulnDB"] = fmt.Sprintf("https://wpvulndb.com/vulnerabilities/%s",
|
||||
strings.TrimPrefix(v.CveID, "WPVDBID-"))
|
||||
return links
|
||||
}
|
||||
|
||||
switch family {
|
||||
case config.RedHat, config.CentOS:
|
||||
links["RHEL-CVE"] = "https://access.redhat.com/security/cve/" + v.CveID
|
||||
for _, advisory := range v.DistroAdvisories {
|
||||
aidURL := strings.Replace(advisory.AdvisoryID, ":", "-", -1)
|
||||
links[advisory.AdvisoryID] = fmt.Sprintf("https://rhn.redhat.com/errata/%s.html", aidURL)
|
||||
}
|
||||
return links
|
||||
case config.Oracle:
|
||||
links["Oracle-CVE"] = fmt.Sprintf("https://linux.oracle.com/cve/%s.html", v.CveID)
|
||||
for _, advisory := range v.DistroAdvisories {
|
||||
links[advisory.AdvisoryID] =
|
||||
fmt.Sprintf("https://linux.oracle.com/errata/%s.html", advisory.AdvisoryID)
|
||||
}
|
||||
return links
|
||||
case config.Amazon:
|
||||
links["RHEL-CVE"] = "https://access.redhat.com/security/cve/" + v.CveID
|
||||
for _, advisory := range v.DistroAdvisories {
|
||||
if strings.HasPrefix(advisory.AdvisoryID, "ALAS2") {
|
||||
links[advisory.AdvisoryID] =
|
||||
fmt.Sprintf("https://alas.aws.amazon.com/AL2/%s.html",
|
||||
strings.Replace(advisory.AdvisoryID, "ALAS2", "ALAS", -1))
|
||||
} else {
|
||||
links[advisory.AdvisoryID] =
|
||||
fmt.Sprintf("https://alas.aws.amazon.com/%s.html", advisory.AdvisoryID)
|
||||
}
|
||||
}
|
||||
return links
|
||||
case config.Ubuntu:
|
||||
links["Ubuntu-CVE"] = "http://people.ubuntu.com/~ubuntu-security/cve/" + v.CveID
|
||||
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)
|
||||
|
||||
}
|
||||
return links
|
||||
}
|
||||
return links
|
||||
}
|
||||
|
||||
// DistroAdvisories is a list of DistroAdvisory
|
||||
type DistroAdvisories []DistroAdvisory
|
||||
|
||||
@@ -800,7 +639,14 @@ type Metasploit struct {
|
||||
URLs []string `json:",omitempty"`
|
||||
}
|
||||
|
||||
// AlertDict has target cve's JPCERT and USCERT alert data
|
||||
// Mitigation has a link and content
|
||||
type Mitigation struct {
|
||||
CveContentType CveContentType `json:"cveContentType,omitempty"`
|
||||
Mitigation string `json:"mitigation,omitempty"`
|
||||
URL string `json:"url,omitempty"`
|
||||
}
|
||||
|
||||
// AlertDict has target cve JPCERT and USCERT alert data
|
||||
type AlertDict struct {
|
||||
Ja []Alert `json:"ja"`
|
||||
En []Alert `json:"en"`
|
||||
@@ -821,7 +667,7 @@ func (a AlertDict) FormatSource() string {
|
||||
// Confidences is a list of Confidence
|
||||
type Confidences []Confidence
|
||||
|
||||
// AppendIfMissing appends confidence to the list if missiong
|
||||
// AppendIfMissing appends confidence to the list if missing
|
||||
func (cs *Confidences) AppendIfMissing(confidence Confidence) {
|
||||
for _, c := range *cs {
|
||||
if c.DetectionMethod == confidence.DetectionMethod {
|
||||
@@ -839,7 +685,7 @@ func (cs Confidences) SortByConfident() Confidences {
|
||||
return cs
|
||||
}
|
||||
|
||||
// Confidence is a ranking how confident the CVE-ID was deteted correctly
|
||||
// Confidence is a ranking how confident the CVE-ID was detected correctly
|
||||
// Score: 0 - 100
|
||||
type Confidence struct {
|
||||
Score int `json:"score"`
|
||||
@@ -887,8 +733,8 @@ const (
|
||||
// GitHubMatchStr is a String representation of GitHubMatch
|
||||
GitHubMatchStr = "GitHubMatch"
|
||||
|
||||
// WPVulnDBMatchStr is a String representation of WordPress VulnDB scanning
|
||||
WPVulnDBMatchStr = "WPVulnDBMatch"
|
||||
// WpScanMatchStr is a String representation of WordPress VulnDB scanning
|
||||
WpScanMatchStr = "WpScanMatch"
|
||||
|
||||
// FailedToGetChangelog is a String representation of FailedToGetChangelog
|
||||
FailedToGetChangelog = "FailedToGetChangelog"
|
||||
@@ -898,36 +744,36 @@ const (
|
||||
)
|
||||
|
||||
var (
|
||||
// CpeNameMatch is a ranking how confident the CVE-ID was deteted correctly
|
||||
// CpeNameMatch is a ranking how confident the CVE-ID was detected correctly
|
||||
CpeNameMatch = Confidence{100, CpeNameMatchStr, 1}
|
||||
|
||||
// YumUpdateSecurityMatch is a ranking how confident the CVE-ID was deteted correctly
|
||||
// YumUpdateSecurityMatch is a ranking how confident the CVE-ID was detected correctly
|
||||
YumUpdateSecurityMatch = Confidence{100, YumUpdateSecurityMatchStr, 2}
|
||||
|
||||
// PkgAuditMatch is a ranking how confident the CVE-ID was deteted correctly
|
||||
// PkgAuditMatch is a ranking how confident the CVE-ID was detected correctly
|
||||
PkgAuditMatch = Confidence{100, PkgAuditMatchStr, 2}
|
||||
|
||||
// OvalMatch is a ranking how confident the CVE-ID was deteted correctly
|
||||
// OvalMatch is a ranking how confident the CVE-ID was detected correctly
|
||||
OvalMatch = Confidence{100, OvalMatchStr, 0}
|
||||
|
||||
// RedHatAPIMatch ranking how confident the CVE-ID was deteted correctly
|
||||
// RedHatAPIMatch ranking how confident the CVE-ID was detected correctly
|
||||
RedHatAPIMatch = Confidence{100, RedHatAPIStr, 0}
|
||||
|
||||
// DebianSecurityTrackerMatch ranking how confident the CVE-ID was deteted correctly
|
||||
// DebianSecurityTrackerMatch ranking how confident the CVE-ID was detected correctly
|
||||
DebianSecurityTrackerMatch = Confidence{100, DebianSecurityTrackerMatchStr, 0}
|
||||
|
||||
// TrivyMatch ranking how confident the CVE-ID was deteted correctly
|
||||
// TrivyMatch ranking how confident the CVE-ID was detected correctly
|
||||
TrivyMatch = Confidence{100, TrivyMatchStr, 0}
|
||||
|
||||
// ChangelogExactMatch is a ranking how confident the CVE-ID was deteted correctly
|
||||
// ChangelogExactMatch is a ranking how confident the CVE-ID was detected correctly
|
||||
ChangelogExactMatch = Confidence{95, ChangelogExactMatchStr, 3}
|
||||
|
||||
// ChangelogLenientMatch is a ranking how confident the CVE-ID was deteted correctly
|
||||
// ChangelogLenientMatch is a ranking how confident the CVE-ID was detected correctly
|
||||
ChangelogLenientMatch = Confidence{50, ChangelogLenientMatchStr, 4}
|
||||
|
||||
// GitHubMatch is a ranking how confident the CVE-ID was deteted correctly
|
||||
// GitHubMatch is a ranking how confident the CVE-ID was detected correctly
|
||||
GitHubMatch = Confidence{97, GitHubMatchStr, 2}
|
||||
|
||||
// WPVulnDBMatch is a ranking how confident the CVE-ID was deteted correctly
|
||||
WPVulnDBMatch = Confidence{100, WPVulnDBMatchStr, 0}
|
||||
// WpScanMatch is a ranking how confident the CVE-ID was detected correctly
|
||||
WpScanMatch = Confidence{100, WpScanMatchStr, 0}
|
||||
)
|
||||
|
||||
@@ -28,10 +28,10 @@ func TestTitles(t *testing.T) {
|
||||
Type: RedHat,
|
||||
Summary: "Summary RedHat",
|
||||
},
|
||||
NvdXML: {
|
||||
Type: NvdXML,
|
||||
Nvd: {
|
||||
Type: Nvd,
|
||||
Summary: "Summary NVD",
|
||||
// Severity is NIOT included in NVD
|
||||
// Severity is NOT included in NVD
|
||||
},
|
||||
},
|
||||
},
|
||||
@@ -42,7 +42,7 @@ func TestTitles(t *testing.T) {
|
||||
Value: "Title1",
|
||||
},
|
||||
{
|
||||
Type: NvdXML,
|
||||
Type: Nvd,
|
||||
Value: "Summary NVD",
|
||||
},
|
||||
{
|
||||
@@ -65,17 +65,17 @@ func TestTitles(t *testing.T) {
|
||||
Type: RedHat,
|
||||
Summary: "Summary RedHat",
|
||||
},
|
||||
NvdXML: {
|
||||
Type: NvdXML,
|
||||
Nvd: {
|
||||
Type: Nvd,
|
||||
Summary: "Summary NVD",
|
||||
// Severity is NIOT included in NVD
|
||||
// Severity is NOT included in NVD
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
out: []CveContentStr{
|
||||
{
|
||||
Type: NvdXML,
|
||||
Type: Nvd,
|
||||
Value: "Summary NVD",
|
||||
},
|
||||
{
|
||||
@@ -98,10 +98,10 @@ func TestTitles(t *testing.T) {
|
||||
},
|
||||
},
|
||||
}
|
||||
for _, tt := range tests {
|
||||
for i, tt := range tests {
|
||||
actual := tt.in.cont.Titles(tt.in.lang, "redhat")
|
||||
if !reflect.DeepEqual(tt.out, actual) {
|
||||
t.Errorf("\nexpected: %v\n actual: %v\n", tt.out, actual)
|
||||
t.Errorf("[%d]\nexpected: %v\n actual: %v\n", i, tt.out, actual)
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -130,10 +130,10 @@ func TestSummaries(t *testing.T) {
|
||||
Type: RedHat,
|
||||
Summary: "Summary RedHat",
|
||||
},
|
||||
NvdXML: {
|
||||
Type: NvdXML,
|
||||
Nvd: {
|
||||
Type: Nvd,
|
||||
Summary: "Summary NVD",
|
||||
// Severity is NIOT included in NVD
|
||||
// Severity is NOT included in NVD
|
||||
},
|
||||
},
|
||||
},
|
||||
@@ -148,7 +148,7 @@ func TestSummaries(t *testing.T) {
|
||||
Value: "Summary RedHat",
|
||||
},
|
||||
{
|
||||
Type: NvdXML,
|
||||
Type: Nvd,
|
||||
Value: "Summary NVD",
|
||||
},
|
||||
},
|
||||
@@ -168,10 +168,10 @@ func TestSummaries(t *testing.T) {
|
||||
Type: RedHat,
|
||||
Summary: "Summary RedHat",
|
||||
},
|
||||
NvdXML: {
|
||||
Type: NvdXML,
|
||||
Nvd: {
|
||||
Type: Nvd,
|
||||
Summary: "Summary NVD",
|
||||
// Severity is NIOT included in NVD
|
||||
// Severity is NOT included in NVD
|
||||
},
|
||||
},
|
||||
},
|
||||
@@ -182,7 +182,7 @@ func TestSummaries(t *testing.T) {
|
||||
Value: "Summary RedHat",
|
||||
},
|
||||
{
|
||||
Type: NvdXML,
|
||||
Type: Nvd,
|
||||
Value: "Summary NVD",
|
||||
},
|
||||
},
|
||||
@@ -219,21 +219,75 @@ func TestCountGroupBySeverity(t *testing.T) {
|
||||
"CVE-2017-0002": {
|
||||
CveID: "CVE-2017-0002",
|
||||
CveContents: CveContents{
|
||||
NvdXML: {
|
||||
Type: NvdXML,
|
||||
Cvss2Score: 6.0,
|
||||
Nvd: {
|
||||
Type: Nvd,
|
||||
Cvss3Score: 6.0,
|
||||
},
|
||||
RedHat: {
|
||||
Type: RedHat,
|
||||
Cvss2Score: 7.0,
|
||||
Cvss3Score: 7.0,
|
||||
},
|
||||
},
|
||||
},
|
||||
"CVE-2017-0003": {
|
||||
CveID: "CVE-2017-0003",
|
||||
CveContents: CveContents{
|
||||
NvdXML: {
|
||||
Type: NvdXML,
|
||||
Nvd: {
|
||||
Type: Nvd,
|
||||
Cvss3Score: 2.0,
|
||||
},
|
||||
},
|
||||
},
|
||||
"CVE-2017-0004": {
|
||||
CveID: "CVE-2017-0004",
|
||||
CveContents: CveContents{
|
||||
Nvd: {
|
||||
Type: Nvd,
|
||||
Cvss3Score: 5.0,
|
||||
},
|
||||
},
|
||||
},
|
||||
"CVE-2017-0005": {
|
||||
CveID: "CVE-2017-0005",
|
||||
},
|
||||
"CVE-2017-0006": {
|
||||
CveID: "CVE-2017-0005",
|
||||
CveContents: CveContents{
|
||||
Nvd: {
|
||||
Type: Nvd,
|
||||
Cvss3Score: 10.0,
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
out: map[string]int{
|
||||
"Critical": 1,
|
||||
"High": 1,
|
||||
"Medium": 1,
|
||||
"Low": 1,
|
||||
"Unknown": 1,
|
||||
},
|
||||
},
|
||||
{
|
||||
in: VulnInfos{
|
||||
"CVE-2017-0002": {
|
||||
CveID: "CVE-2017-0002",
|
||||
CveContents: CveContents{
|
||||
Nvd: {
|
||||
Type: Nvd,
|
||||
Cvss2Score: 1.0,
|
||||
},
|
||||
RedHat: {
|
||||
Type: RedHat,
|
||||
Cvss3Score: 7.0,
|
||||
},
|
||||
},
|
||||
},
|
||||
"CVE-2017-0003": {
|
||||
CveID: "CVE-2017-0003",
|
||||
CveContents: CveContents{
|
||||
Nvd: {
|
||||
Type: Nvd,
|
||||
Cvss2Score: 2.0,
|
||||
},
|
||||
},
|
||||
@@ -241,8 +295,8 @@ func TestCountGroupBySeverity(t *testing.T) {
|
||||
"CVE-2017-0004": {
|
||||
CveID: "CVE-2017-0004",
|
||||
CveContents: CveContents{
|
||||
NvdXML: {
|
||||
Type: NvdXML,
|
||||
Nvd: {
|
||||
Type: Nvd,
|
||||
Cvss2Score: 5.0,
|
||||
},
|
||||
},
|
||||
@@ -250,21 +304,31 @@ func TestCountGroupBySeverity(t *testing.T) {
|
||||
"CVE-2017-0005": {
|
||||
CveID: "CVE-2017-0005",
|
||||
},
|
||||
"CVE-2017-0006": {
|
||||
CveID: "CVE-2017-0005",
|
||||
CveContents: CveContents{
|
||||
Nvd: {
|
||||
Type: Nvd,
|
||||
Cvss2Score: 10.0,
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
out: map[string]int{
|
||||
"High": 1,
|
||||
"Medium": 1,
|
||||
"Low": 1,
|
||||
"Unknown": 1,
|
||||
"Critical": 1,
|
||||
"High": 1,
|
||||
"Medium": 1,
|
||||
"Low": 1,
|
||||
"Unknown": 1,
|
||||
},
|
||||
},
|
||||
}
|
||||
for _, tt := range tests {
|
||||
for i, tt := range tests {
|
||||
actual := tt.in.CountGroupBySeverity()
|
||||
for k := range tt.out {
|
||||
if tt.out[k] != actual[k] {
|
||||
t.Errorf("\nexpected %s: %d\n actual %d\n",
|
||||
k, tt.out[k], actual[k])
|
||||
t.Errorf("[%d]\nexpected %s: %d\n actual %d\n",
|
||||
i, k, tt.out[k], actual[k])
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -275,13 +339,14 @@ func TestToSortedSlice(t *testing.T) {
|
||||
in VulnInfos
|
||||
out []VulnInfo
|
||||
}{
|
||||
//0
|
||||
{
|
||||
in: VulnInfos{
|
||||
"CVE-2017-0002": {
|
||||
CveID: "CVE-2017-0002",
|
||||
CveContents: CveContents{
|
||||
NvdXML: {
|
||||
Type: NvdXML,
|
||||
Nvd: {
|
||||
Type: Nvd,
|
||||
Cvss2Score: 6.0,
|
||||
},
|
||||
RedHat: {
|
||||
@@ -293,8 +358,8 @@ func TestToSortedSlice(t *testing.T) {
|
||||
"CVE-2017-0001": {
|
||||
CveID: "CVE-2017-0001",
|
||||
CveContents: CveContents{
|
||||
NvdXML: {
|
||||
Type: NvdXML,
|
||||
Nvd: {
|
||||
Type: Nvd,
|
||||
Cvss2Score: 7.0,
|
||||
},
|
||||
RedHat: {
|
||||
@@ -308,8 +373,8 @@ func TestToSortedSlice(t *testing.T) {
|
||||
{
|
||||
CveID: "CVE-2017-0001",
|
||||
CveContents: CveContents{
|
||||
NvdXML: {
|
||||
Type: NvdXML,
|
||||
Nvd: {
|
||||
Type: Nvd,
|
||||
Cvss2Score: 7.0,
|
||||
},
|
||||
RedHat: {
|
||||
@@ -321,8 +386,8 @@ func TestToSortedSlice(t *testing.T) {
|
||||
{
|
||||
CveID: "CVE-2017-0002",
|
||||
CveContents: CveContents{
|
||||
NvdXML: {
|
||||
Type: NvdXML,
|
||||
Nvd: {
|
||||
Type: Nvd,
|
||||
Cvss2Score: 6.0,
|
||||
},
|
||||
RedHat: {
|
||||
@@ -333,14 +398,14 @@ func TestToSortedSlice(t *testing.T) {
|
||||
},
|
||||
},
|
||||
},
|
||||
// When max scores are the same, sort by CVE-ID
|
||||
//[1] When max scores are the same, sort by CVE-ID
|
||||
{
|
||||
in: VulnInfos{
|
||||
"CVE-2017-0002": {
|
||||
CveID: "CVE-2017-0002",
|
||||
CveContents: CveContents{
|
||||
NvdXML: {
|
||||
Type: NvdXML,
|
||||
Nvd: {
|
||||
Type: Nvd,
|
||||
Cvss2Score: 6.0,
|
||||
},
|
||||
RedHat: {
|
||||
@@ -354,7 +419,7 @@ func TestToSortedSlice(t *testing.T) {
|
||||
CveContents: CveContents{
|
||||
RedHat: {
|
||||
Type: RedHat,
|
||||
Cvss2Score: 7.0,
|
||||
Cvss3Score: 7.0,
|
||||
},
|
||||
},
|
||||
},
|
||||
@@ -365,15 +430,15 @@ func TestToSortedSlice(t *testing.T) {
|
||||
CveContents: CveContents{
|
||||
RedHat: {
|
||||
Type: RedHat,
|
||||
Cvss2Score: 7.0,
|
||||
Cvss3Score: 7.0,
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
CveID: "CVE-2017-0002",
|
||||
CveContents: CveContents{
|
||||
NvdXML: {
|
||||
Type: NvdXML,
|
||||
Nvd: {
|
||||
Type: Nvd,
|
||||
Cvss2Score: 6.0,
|
||||
},
|
||||
RedHat: {
|
||||
@@ -384,7 +449,7 @@ func TestToSortedSlice(t *testing.T) {
|
||||
},
|
||||
},
|
||||
},
|
||||
// When there are no cvss scores, sort by severity
|
||||
//[2] When there are no cvss scores, sort by severity
|
||||
{
|
||||
in: VulnInfos{
|
||||
"CVE-2017-0002": {
|
||||
@@ -392,7 +457,7 @@ func TestToSortedSlice(t *testing.T) {
|
||||
CveContents: CveContents{
|
||||
Ubuntu: {
|
||||
Type: Ubuntu,
|
||||
Cvss2Severity: "High",
|
||||
Cvss3Severity: "High",
|
||||
},
|
||||
},
|
||||
},
|
||||
@@ -401,7 +466,7 @@ func TestToSortedSlice(t *testing.T) {
|
||||
CveContents: CveContents{
|
||||
Ubuntu: {
|
||||
Type: Ubuntu,
|
||||
Cvss2Severity: "Low",
|
||||
Cvss3Severity: "Low",
|
||||
},
|
||||
},
|
||||
},
|
||||
@@ -412,7 +477,7 @@ func TestToSortedSlice(t *testing.T) {
|
||||
CveContents: CveContents{
|
||||
Ubuntu: {
|
||||
Type: Ubuntu,
|
||||
Cvss2Severity: "High",
|
||||
Cvss3Severity: "High",
|
||||
},
|
||||
},
|
||||
},
|
||||
@@ -421,17 +486,17 @@ func TestToSortedSlice(t *testing.T) {
|
||||
CveContents: CveContents{
|
||||
Ubuntu: {
|
||||
Type: Ubuntu,
|
||||
Cvss2Severity: "Low",
|
||||
Cvss3Severity: "Low",
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
for _, tt := range tests {
|
||||
for i, tt := range tests {
|
||||
actual := tt.in.ToSortedSlice()
|
||||
if !reflect.DeepEqual(tt.out, actual) {
|
||||
t.Errorf("\nexpected: %v\n actual: %v\n", tt.out, actual)
|
||||
t.Errorf("[%d]\nexpected: %v\n actual: %v\n", i, tt.out, actual)
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -456,29 +521,36 @@ func TestCvss2Scores(t *testing.T) {
|
||||
Cvss2Score: 8.0,
|
||||
Cvss2Vector: "AV:N/AC:L/Au:N/C:N/I:N/A:P",
|
||||
},
|
||||
NvdXML: {
|
||||
Type: NvdXML,
|
||||
Nvd: {
|
||||
Type: Nvd,
|
||||
Cvss2Score: 8.1,
|
||||
Cvss2Vector: "AV:N/AC:L/Au:N/C:N/I:N/A:P",
|
||||
Cvss2Severity: "HIGH",
|
||||
},
|
||||
//v3
|
||||
RedHatAPI: {
|
||||
Type: RedHatAPI,
|
||||
Cvss3Score: 8.1,
|
||||
Cvss3Vector: "AV:N/AC:L/Au:N/C:N/I:N/A:P",
|
||||
Cvss3Severity: "HIGH",
|
||||
},
|
||||
},
|
||||
},
|
||||
out: []CveContentCvss{
|
||||
{
|
||||
Type: NvdXML,
|
||||
Type: RedHat,
|
||||
Value: Cvss{
|
||||
Type: CVSS2,
|
||||
Score: 8.1,
|
||||
Score: 8.0,
|
||||
Vector: "AV:N/AC:L/Au:N/C:N/I:N/A:P",
|
||||
Severity: "HIGH",
|
||||
},
|
||||
},
|
||||
{
|
||||
Type: RedHat,
|
||||
Type: Nvd,
|
||||
Value: Cvss{
|
||||
Type: CVSS2,
|
||||
Score: 8.0,
|
||||
Score: 8.1,
|
||||
Vector: "AV:N/AC:L/Au:N/C:N/I:N/A:P",
|
||||
Severity: "HIGH",
|
||||
},
|
||||
@@ -501,7 +573,7 @@ func TestCvss2Scores(t *testing.T) {
|
||||
},
|
||||
}
|
||||
for i, tt := range tests {
|
||||
actual := tt.in.Cvss2Scores("redhat")
|
||||
actual := tt.in.Cvss2Scores()
|
||||
if !reflect.DeepEqual(tt.out, actual) {
|
||||
t.Errorf("[%d]\nexpected: %v\n actual: %v\n", i, tt.out, actual)
|
||||
}
|
||||
@@ -513,6 +585,7 @@ func TestMaxCvss2Scores(t *testing.T) {
|
||||
in VulnInfo
|
||||
out CveContentCvss
|
||||
}{
|
||||
// 0
|
||||
{
|
||||
in: VulnInfo{
|
||||
CveContents: CveContents{
|
||||
@@ -528,11 +601,11 @@ func TestMaxCvss2Scores(t *testing.T) {
|
||||
Cvss2Score: 8.0,
|
||||
Cvss2Vector: "AV:N/AC:L/Au:N/C:N/I:N/A:P",
|
||||
},
|
||||
NvdXML: {
|
||||
Type: NvdXML,
|
||||
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
|
||||
// Severity is NOT included in NVD
|
||||
},
|
||||
},
|
||||
},
|
||||
@@ -546,26 +619,6 @@ func TestMaxCvss2Scores(t *testing.T) {
|
||||
},
|
||||
},
|
||||
},
|
||||
// Severity in OVAL
|
||||
{
|
||||
in: VulnInfo{
|
||||
CveContents: CveContents{
|
||||
Ubuntu: {
|
||||
Type: Ubuntu,
|
||||
Cvss2Severity: "HIGH",
|
||||
},
|
||||
},
|
||||
},
|
||||
out: CveContentCvss{
|
||||
Type: Ubuntu,
|
||||
Value: Cvss{
|
||||
Type: CVSS2,
|
||||
Score: 8.9,
|
||||
CalculatedBySeverity: true,
|
||||
Severity: "HIGH",
|
||||
},
|
||||
},
|
||||
},
|
||||
// Empty
|
||||
{
|
||||
in: VulnInfo{},
|
||||
@@ -602,8 +655,8 @@ func TestCvss3Scores(t *testing.T) {
|
||||
Cvss3Score: 8.0,
|
||||
Cvss3Vector: "AV:N/AC:H/PR:N/UI:N/S:U/C:L/I:L/A:L",
|
||||
},
|
||||
NvdXML: {
|
||||
Type: NvdXML,
|
||||
Nvd: {
|
||||
Type: Nvd,
|
||||
Cvss2Score: 8.1,
|
||||
Cvss2Vector: "AV:N/AC:H/PR:N/UI:N/S:U/C:L/I:L/A:L",
|
||||
Cvss2Severity: "HIGH",
|
||||
@@ -622,16 +675,38 @@ func TestCvss3Scores(t *testing.T) {
|
||||
},
|
||||
},
|
||||
},
|
||||
// [1] Severity in OVAL
|
||||
{
|
||||
in: VulnInfo{
|
||||
CveContents: CveContents{
|
||||
Ubuntu: {
|
||||
Type: Ubuntu,
|
||||
Cvss3Severity: "HIGH",
|
||||
},
|
||||
},
|
||||
},
|
||||
out: []CveContentCvss{
|
||||
{
|
||||
Type: Ubuntu,
|
||||
Value: Cvss{
|
||||
Type: CVSS3,
|
||||
Score: 8.9,
|
||||
CalculatedBySeverity: true,
|
||||
Severity: "HIGH",
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
// Empty
|
||||
{
|
||||
in: VulnInfo{},
|
||||
out: nil,
|
||||
},
|
||||
}
|
||||
for _, tt := range tests {
|
||||
for i, tt := range tests {
|
||||
actual := tt.in.Cvss3Scores()
|
||||
if !reflect.DeepEqual(tt.out, actual) {
|
||||
t.Errorf("\nexpected: %v\n actual: %v\n", tt.out, actual)
|
||||
t.Errorf("[%d]\nexpected: %v\n actual: %v\n", i, tt.out, actual)
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -692,8 +767,8 @@ func TestMaxCvssScores(t *testing.T) {
|
||||
{
|
||||
in: VulnInfo{
|
||||
CveContents: CveContents{
|
||||
NvdXML: {
|
||||
Type: NvdXML,
|
||||
Nvd: {
|
||||
Type: Nvd,
|
||||
Cvss3Score: 7.0,
|
||||
},
|
||||
RedHat: {
|
||||
@@ -703,10 +778,10 @@ func TestMaxCvssScores(t *testing.T) {
|
||||
},
|
||||
},
|
||||
out: CveContentCvss{
|
||||
Type: RedHat,
|
||||
Type: Nvd,
|
||||
Value: Cvss{
|
||||
Type: CVSS2,
|
||||
Score: 8.0,
|
||||
Type: CVSS3,
|
||||
Score: 7.0,
|
||||
},
|
||||
},
|
||||
},
|
||||
@@ -733,14 +808,14 @@ func TestMaxCvssScores(t *testing.T) {
|
||||
CveContents: CveContents{
|
||||
Ubuntu: {
|
||||
Type: Ubuntu,
|
||||
Cvss2Severity: "HIGH",
|
||||
Cvss3Severity: "HIGH",
|
||||
},
|
||||
},
|
||||
},
|
||||
out: CveContentCvss{
|
||||
Type: Ubuntu,
|
||||
Value: Cvss{
|
||||
Type: CVSS2,
|
||||
Type: CVSS3,
|
||||
Score: 8.9,
|
||||
CalculatedBySeverity: true,
|
||||
Severity: "HIGH",
|
||||
@@ -753,21 +828,22 @@ func TestMaxCvssScores(t *testing.T) {
|
||||
CveContents: CveContents{
|
||||
Ubuntu: {
|
||||
Type: Ubuntu,
|
||||
Cvss2Severity: "MEDIUM",
|
||||
Cvss3Severity: "MEDIUM",
|
||||
},
|
||||
NvdXML: {
|
||||
Type: NvdXML,
|
||||
Nvd: {
|
||||
Type: Nvd,
|
||||
Cvss2Score: 7.0,
|
||||
Cvss2Severity: "HIGH",
|
||||
},
|
||||
},
|
||||
},
|
||||
out: CveContentCvss{
|
||||
Type: NvdXML,
|
||||
Type: Ubuntu,
|
||||
Value: Cvss{
|
||||
Type: CVSS2,
|
||||
Score: 7.0,
|
||||
Severity: "HIGH",
|
||||
Type: CVSS3,
|
||||
Score: 6.9,
|
||||
Severity: "MEDIUM",
|
||||
CalculatedBySeverity: true,
|
||||
},
|
||||
},
|
||||
},
|
||||
@@ -783,23 +859,23 @@ func TestMaxCvssScores(t *testing.T) {
|
||||
out: CveContentCvss{
|
||||
Type: "Vendor",
|
||||
Value: Cvss{
|
||||
Type: CVSS2,
|
||||
Type: CVSS3,
|
||||
Score: 8.9,
|
||||
CalculatedBySeverity: true,
|
||||
Vector: "-",
|
||||
Severity: "HIGH",
|
||||
},
|
||||
},
|
||||
},
|
||||
//5
|
||||
{
|
||||
in: VulnInfo{
|
||||
CveContents: CveContents{
|
||||
Ubuntu: {
|
||||
Type: Ubuntu,
|
||||
Cvss2Severity: "MEDIUM",
|
||||
Cvss3Severity: "MEDIUM",
|
||||
},
|
||||
NvdXML: {
|
||||
Type: NvdXML,
|
||||
Nvd: {
|
||||
Type: Nvd,
|
||||
Cvss2Score: 4.0,
|
||||
Cvss2Severity: "MEDIUM",
|
||||
},
|
||||
@@ -811,11 +887,12 @@ func TestMaxCvssScores(t *testing.T) {
|
||||
},
|
||||
},
|
||||
out: CveContentCvss{
|
||||
Type: NvdXML,
|
||||
Type: "Vendor",
|
||||
Value: Cvss{
|
||||
Type: CVSS2,
|
||||
Score: 4,
|
||||
Severity: "MEDIUM",
|
||||
Type: CVSS3,
|
||||
Score: 8.9,
|
||||
Severity: "HIGH",
|
||||
CalculatedBySeverity: true,
|
||||
},
|
||||
},
|
||||
},
|
||||
@@ -854,17 +931,17 @@ func TestFormatMaxCvssScore(t *testing.T) {
|
||||
},
|
||||
RedHat: {
|
||||
Type: RedHat,
|
||||
Cvss2Severity: "HIGH",
|
||||
Cvss3Severity: "HIGH",
|
||||
Cvss3Score: 8.0,
|
||||
},
|
||||
NvdXML: {
|
||||
Type: NvdXML,
|
||||
Nvd: {
|
||||
Type: Nvd,
|
||||
Cvss2Score: 8.1,
|
||||
// Severity is NIOT included in NVD
|
||||
// Severity is NOT included in NVD
|
||||
},
|
||||
},
|
||||
},
|
||||
out: "8.3 HIGH (jvn)",
|
||||
out: "8.0 HIGH (redhat)",
|
||||
},
|
||||
{
|
||||
in: VulnInfo{
|
||||
@@ -881,8 +958,8 @@ func TestFormatMaxCvssScore(t *testing.T) {
|
||||
Cvss3Severity: "HIGH",
|
||||
Cvss3Score: 9.9,
|
||||
},
|
||||
NvdXML: {
|
||||
Type: NvdXML,
|
||||
Nvd: {
|
||||
Type: Nvd,
|
||||
Cvss2Score: 8.1,
|
||||
},
|
||||
},
|
||||
@@ -890,10 +967,10 @@ func TestFormatMaxCvssScore(t *testing.T) {
|
||||
out: "9.9 HIGH (redhat)",
|
||||
},
|
||||
}
|
||||
for _, tt := range tests {
|
||||
for i, tt := range tests {
|
||||
actual := tt.in.FormatMaxCvssScore()
|
||||
if !reflect.DeepEqual(tt.out, actual) {
|
||||
t.Errorf("\nexpected: %v\n actual: %v\n", tt.out, actual)
|
||||
t.Errorf("[%d]\nexpected: %v\n actual: %v\n", i, tt.out, actual)
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -922,7 +999,7 @@ func TestSortPackageStatues(t *testing.T) {
|
||||
}
|
||||
}
|
||||
|
||||
func TestStorePackageStatueses(t *testing.T) {
|
||||
func TestStorePackageStatuses(t *testing.T) {
|
||||
var tests = []struct {
|
||||
pkgstats PackageFixStatuses
|
||||
in PackageFixStatus
|
||||
@@ -985,7 +1062,7 @@ func TestAppendIfMissing(t *testing.T) {
|
||||
}
|
||||
}
|
||||
|
||||
func TestSortByConfiden(t *testing.T) {
|
||||
func TestSortByConfident(t *testing.T) {
|
||||
var tests = []struct {
|
||||
in Confidences
|
||||
out Confidences
|
||||
|
||||
1
msf/empty.go
Normal file
1
msf/empty.go
Normal file
@@ -0,0 +1 @@
|
||||
package msf
|
||||
26
msf/msf.go
26
msf/msf.go
@@ -1,15 +1,11 @@
|
||||
// +build !scanner
|
||||
|
||||
package msf
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"net/http"
|
||||
|
||||
cnf "github.com/future-architect/vuls/config"
|
||||
"github.com/future-architect/vuls/models"
|
||||
"github.com/parnurzeal/gorequest"
|
||||
"github.com/takuzoo3868/go-msfdb/db"
|
||||
metasploitmodels "github.com/takuzoo3868/go-msfdb/models"
|
||||
"golang.org/x/xerrors"
|
||||
)
|
||||
|
||||
// FillWithMetasploit fills metasploit module information that has in module
|
||||
@@ -53,21 +49,3 @@ func ConvertToModels(ms []*metasploitmodels.Metasploit) (modules []models.Metasp
|
||||
}
|
||||
return modules
|
||||
}
|
||||
|
||||
// CheckHTTPHealth do health check
|
||||
func CheckHTTPHealth() error {
|
||||
if !cnf.Conf.Metasploit.IsFetchViaHTTP() {
|
||||
return nil
|
||||
}
|
||||
|
||||
url := fmt.Sprintf("%s/health", cnf.Conf.Metasploit.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 xerrors.Errorf("Failed to connect to metasploit server. url: %s, errs: %w", url, errs)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
@@ -1,3 +1,5 @@
|
||||
// +build !scanner
|
||||
|
||||
package oval
|
||||
|
||||
import (
|
||||
|
||||
@@ -1,3 +1,5 @@
|
||||
// +build !scanner
|
||||
|
||||
package oval
|
||||
|
||||
import (
|
||||
@@ -59,7 +61,7 @@ func (o DebianBase) update(r *models.ScanResult, defPacks defPacks) {
|
||||
}
|
||||
|
||||
// Update package status of source packages.
|
||||
// In the case of Debian based Linux, sometimes source package name is difined as affected package in OVAL.
|
||||
// In the case of Debian based Linux, sometimes source package name is defined as affected package in OVAL.
|
||||
// To display binary package name showed in apt-get, need to convert source name to binary name.
|
||||
for binName := range defPacks.binpkgFixstat {
|
||||
if srcPack, ok := r.SrcPackages.FindByBinName(binName); ok {
|
||||
@@ -209,7 +211,7 @@ func NewUbuntu() Ubuntu {
|
||||
|
||||
// FillWithOval returns scan result after updating CVE info by OVAL
|
||||
func (o Ubuntu) FillWithOval(driver db.DB, r *models.ScanResult) (nCVEs int, err error) {
|
||||
switch major(r.Release) {
|
||||
switch util.Major(r.Release) {
|
||||
case "14":
|
||||
kernelNamesInOval := []string{
|
||||
"linux-aws",
|
||||
@@ -359,7 +361,7 @@ func (o Ubuntu) fillWithOval(driver db.DB, r *models.ScanResult, kernelNamesInOv
|
||||
if v, ok := r.Packages[linuxImage]; ok {
|
||||
runningKernelVersion = v.Version
|
||||
} else {
|
||||
util.Log.Warnf("Unable to detect vulns of running kernel because the version of the runnning kernel is unknown. server: %s",
|
||||
util.Log.Warnf("Unable to detect vulns of running kernel because the version of the running kernel is unknown. server: %s",
|
||||
r.ServerName)
|
||||
}
|
||||
|
||||
@@ -387,13 +389,13 @@ func (o Ubuntu) fillWithOval(driver db.DB, r *models.ScanResult, kernelNamesInOv
|
||||
}
|
||||
for srcPackName, srcPack := range r.SrcPackages {
|
||||
copiedSourcePkgs[srcPackName] = srcPack
|
||||
targetBianryNames := []string{}
|
||||
targetBinaryNames := []string{}
|
||||
for _, n := range srcPack.BinaryNames {
|
||||
if n == kernelPkgInOVAL || !strings.HasPrefix(n, "linux-") {
|
||||
targetBianryNames = append(targetBianryNames, n)
|
||||
targetBinaryNames = append(targetBinaryNames, n)
|
||||
}
|
||||
}
|
||||
srcPack.BinaryNames = targetBianryNames
|
||||
srcPack.BinaryNames = targetBinaryNames
|
||||
r.SrcPackages[srcPackName] = srcPack
|
||||
}
|
||||
|
||||
|
||||
@@ -1,3 +1,5 @@
|
||||
// +build !scanner
|
||||
|
||||
package oval
|
||||
|
||||
import (
|
||||
|
||||
1
oval/empty.go
Normal file
1
oval/empty.go
Normal file
@@ -0,0 +1 @@
|
||||
package oval
|
||||
32
oval/oval.go
32
oval/oval.go
@@ -1,9 +1,9 @@
|
||||
// +build !scanner
|
||||
|
||||
package oval
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"net/http"
|
||||
"time"
|
||||
|
||||
cnf "github.com/future-architect/vuls/config"
|
||||
@@ -16,7 +16,6 @@ import (
|
||||
|
||||
// Client is the interface of OVAL client.
|
||||
type Client interface {
|
||||
CheckHTTPHealth() error
|
||||
FillWithOval(db.DB, *models.ScanResult) (int, error)
|
||||
|
||||
// CheckIfOvalFetched checks if oval entries are in DB by family, release.
|
||||
@@ -29,25 +28,6 @@ type Base struct {
|
||||
family string
|
||||
}
|
||||
|
||||
// CheckHTTPHealth do health check
|
||||
func (b Base) CheckHTTPHealth() error {
|
||||
if !cnf.Conf.OvalDict.IsFetchViaHTTP() {
|
||||
return nil
|
||||
}
|
||||
|
||||
url := fmt.Sprintf("%s/health", cnf.Conf.OvalDict.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 xerrors.Errorf("Failed to request to OVAL server. url: %s, errs: %w",
|
||||
url, errs)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// CheckIfOvalFetched checks if oval entries are in DB by family, release.
|
||||
func (b Base) CheckIfOvalFetched(driver db.DB, osFamily, release string) (fetched bool, err error) {
|
||||
if !cnf.Conf.OvalDict.IsFetchViaHTTP() {
|
||||
@@ -61,11 +41,11 @@ func (b Base) CheckIfOvalFetched(driver db.DB, osFamily, release string) (fetche
|
||||
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, xerrors.Errorf("HTTP GET error, url: %s, resp: %v, err: %w", url, resp, errs)
|
||||
return false, xerrors.Errorf("HTTP GET error, url: %s, resp: %v, err: %s", url, resp, errs)
|
||||
}
|
||||
count := 0
|
||||
if err := json.Unmarshal([]byte(body), &count); err != nil {
|
||||
return false, xerrors.Errorf("Failed to Unmarshall. body: %s, err: %w", body, err)
|
||||
return false, xerrors.Errorf("Failed to Unmarshal. body: %s, err: %w", body, err)
|
||||
}
|
||||
return 0 < count, nil
|
||||
}
|
||||
@@ -79,11 +59,11 @@ func (b Base) CheckIfOvalFresh(driver db.DB, osFamily, release string) (ok bool,
|
||||
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, xerrors.Errorf("HTTP GET error, url: %s, resp: %v, err: %w", url, resp, errs)
|
||||
return false, xerrors.Errorf("HTTP GET error, url: %s, resp: %v, err: %s", url, resp, errs)
|
||||
}
|
||||
|
||||
if err := json.Unmarshal([]byte(body), &lastModified); err != nil {
|
||||
return false, xerrors.Errorf("Failed to Unmarshall. body: %s, err: %w", body, err)
|
||||
return false, xerrors.Errorf("Failed to Unmarshal. body: %s, err: %w", body, err)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -1,3 +1,5 @@
|
||||
// +build !scanner
|
||||
|
||||
package oval
|
||||
|
||||
import (
|
||||
@@ -171,17 +173,15 @@ func (o RedHatBase) convertToModel(cveID string, def *ovalmodels.Definition) *mo
|
||||
score2, vec2 := o.parseCvss2(cve.Cvss2)
|
||||
score3, vec3 := o.parseCvss3(cve.Cvss3)
|
||||
|
||||
severity := def.Advisory.Severity
|
||||
sev2, sev3, severity := "", "", def.Advisory.Severity
|
||||
if cve.Impact != "" {
|
||||
severity = cve.Impact
|
||||
}
|
||||
|
||||
sev2, sev3 := "", ""
|
||||
if score2 == 0 {
|
||||
sev2 = severity
|
||||
}
|
||||
if score3 == 0 {
|
||||
if severity != "None" {
|
||||
sev3 = severity
|
||||
if score2 != 0 {
|
||||
sev2 = severity
|
||||
}
|
||||
}
|
||||
|
||||
// CWE-ID in RedHat OVAL may have multiple cweIDs separated by space
|
||||
|
||||
@@ -1,3 +1,5 @@
|
||||
// +build !scanner
|
||||
|
||||
package oval
|
||||
|
||||
import (
|
||||
|
||||
@@ -1,3 +1,5 @@
|
||||
// +build !scanner
|
||||
|
||||
package oval
|
||||
|
||||
import (
|
||||
|
||||
57
oval/util.go
57
oval/util.go
@@ -1,10 +1,11 @@
|
||||
// +build !scanner
|
||||
|
||||
package oval
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"net/http"
|
||||
"regexp"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"github.com/cenkalti/backoff"
|
||||
@@ -76,6 +77,7 @@ type request struct {
|
||||
arch string
|
||||
binaryPackNames []string
|
||||
isSrcPack bool
|
||||
modularityLabel string // RHEL 8 or later only
|
||||
}
|
||||
|
||||
type response struct {
|
||||
@@ -145,7 +147,7 @@ func getDefsByPackNameViaHTTP(r *models.ScanResult) (
|
||||
select {
|
||||
case res := <-resChan:
|
||||
for _, def := range res.defs {
|
||||
affected, notFixedYet, fixedIn := isOvalDefAffected(def, res.request, r.Family, r.RunningKernel)
|
||||
affected, notFixedYet, fixedIn := isOvalDefAffected(def, res.request, r.Family, r.RunningKernel, r.EnabledDnfModules)
|
||||
if !affected {
|
||||
continue
|
||||
}
|
||||
@@ -193,7 +195,7 @@ func httpGet(url string, req request, resChan chan<- response, errChan chan<- er
|
||||
if count == retryMax {
|
||||
return nil
|
||||
}
|
||||
return xerrors.Errorf("HTTP GET error, url: %s, resp: %v, err: %w", url, resp, errs)
|
||||
return xerrors.Errorf("HTTP GET error, url: %s, resp: %v, err: %s", url, resp, errs)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
@@ -212,7 +214,7 @@ func httpGet(url string, req request, resChan chan<- response, errChan chan<- er
|
||||
|
||||
defs := []ovalmodels.Definition{}
|
||||
if err := json.Unmarshal([]byte(body), &defs); err != nil {
|
||||
errChan <- xerrors.Errorf("Failed to Unmarshall. body: %s, err: %w", body, err)
|
||||
errChan <- xerrors.Errorf("Failed to Unmarshal. body: %s, err: %w", body, err)
|
||||
return
|
||||
}
|
||||
resChan <- response{
|
||||
@@ -248,7 +250,7 @@ func getDefsByPackNameFromOvalDB(driver db.DB, r *models.ScanResult) (relatedDef
|
||||
return relatedDefs, xerrors.Errorf("Failed to get %s OVAL info by package: %#v, err: %w", r.Family, req, err)
|
||||
}
|
||||
for _, def := range definitions {
|
||||
affected, notFixedYet, fixedIn := isOvalDefAffected(def, req, r.Family, r.RunningKernel)
|
||||
affected, notFixedYet, fixedIn := isOvalDefAffected(def, req, r.Family, r.RunningKernel, r.EnabledDnfModules)
|
||||
if !affected {
|
||||
continue
|
||||
}
|
||||
@@ -275,29 +277,33 @@ func getDefsByPackNameFromOvalDB(driver db.DB, r *models.ScanResult) (relatedDef
|
||||
return
|
||||
}
|
||||
|
||||
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, fixedIn string) {
|
||||
func isOvalDefAffected(def ovalmodels.Definition, req request, family string, running models.Kernel, enabledMods []string) (affected, notFixedYet bool, fixedIn string) {
|
||||
for _, ovalPack := range def.AffectedPacks {
|
||||
if req.packName != ovalPack.Name {
|
||||
continue
|
||||
}
|
||||
|
||||
isModularityLabelEmptyOrSame := false
|
||||
if ovalPack.ModularityLabel != "" {
|
||||
for _, mod := range enabledMods {
|
||||
if mod == ovalPack.ModularityLabel {
|
||||
isModularityLabelEmptyOrSame = true
|
||||
break
|
||||
}
|
||||
}
|
||||
} else {
|
||||
isModularityLabelEmptyOrSame = true
|
||||
}
|
||||
if !isModularityLabelEmptyOrSame {
|
||||
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) {
|
||||
if util.Major(ovalPack.Version) != util.Major(running.Release) {
|
||||
continue
|
||||
}
|
||||
}
|
||||
@@ -334,7 +340,7 @@ func isOvalDefAffected(def ovalmodels.Definition, req request, family string, ru
|
||||
}
|
||||
|
||||
// But CentOS can't judge whether fixed or unfixed.
|
||||
// Because fixed state in RHEL's OVAL is different.
|
||||
// Because fixed state in RHEL OVAL is different.
|
||||
// So, it have to be judged version comparison.
|
||||
|
||||
// `offline` or `fast` scan mode can't get a updatable version.
|
||||
@@ -357,9 +363,6 @@ func isOvalDefAffected(def ovalmodels.Definition, req request, family string, ru
|
||||
return false, false, ""
|
||||
}
|
||||
|
||||
var centosVerPattern = regexp.MustCompile(`\.[es]l(\d+)(?:_\d+)?(?:\.centos)?`)
|
||||
var esVerPattern = regexp.MustCompile(`\.el(\d+)(?:_\d+)?`)
|
||||
|
||||
func lessThan(family, newVer string, packInOVAL ovalmodels.Package) (bool, error) {
|
||||
switch family {
|
||||
case config.Debian,
|
||||
@@ -395,8 +398,8 @@ func lessThan(family, newVer string, packInOVAL ovalmodels.Package) (bool, error
|
||||
|
||||
case config.RedHat,
|
||||
config.CentOS:
|
||||
vera := rpmver.NewVersion(centosVerPattern.ReplaceAllString(newVer, ".el$1"))
|
||||
verb := rpmver.NewVersion(esVerPattern.ReplaceAllString(packInOVAL.Version, ".el$1"))
|
||||
vera := rpmver.NewVersion(centOSVersionToRHEL(newVer))
|
||||
verb := rpmver.NewVersion(packInOVAL.Version)
|
||||
return vera.LessThan(verb), nil
|
||||
|
||||
default:
|
||||
@@ -404,3 +407,9 @@ func lessThan(family, newVer string, packInOVAL ovalmodels.Package) (bool, error
|
||||
}
|
||||
return false, xerrors.Errorf("Package version comparison not supported: %s", family)
|
||||
}
|
||||
|
||||
var centosVerPattern = regexp.MustCompile(`\.[es]l(\d+)(?:_\d+)?(?:\.centos)?`)
|
||||
|
||||
func centOSVersionToRHEL(ver string) string {
|
||||
return centosVerPattern.ReplaceAllString(ver, ".el$1")
|
||||
}
|
||||
|
||||
@@ -1,3 +1,5 @@
|
||||
// +build !scanner
|
||||
|
||||
package oval
|
||||
|
||||
import (
|
||||
@@ -16,7 +18,7 @@ func TestUpsert(t *testing.T) {
|
||||
def ovalmodels.Definition
|
||||
packName string
|
||||
fixStat fixStat
|
||||
upserted bool
|
||||
upsert bool
|
||||
out ovalResult
|
||||
}{
|
||||
//insert
|
||||
@@ -30,7 +32,7 @@ func TestUpsert(t *testing.T) {
|
||||
notFixedYet: true,
|
||||
fixedIn: "1.0.0",
|
||||
},
|
||||
upserted: false,
|
||||
upsert: false,
|
||||
out: ovalResult{
|
||||
[]defPacks{
|
||||
{
|
||||
@@ -83,7 +85,7 @@ func TestUpsert(t *testing.T) {
|
||||
notFixedYet: false,
|
||||
fixedIn: "3.0.0",
|
||||
},
|
||||
upserted: true,
|
||||
upsert: true,
|
||||
out: ovalResult{
|
||||
[]defPacks{
|
||||
{
|
||||
@@ -117,9 +119,9 @@ func TestUpsert(t *testing.T) {
|
||||
},
|
||||
}
|
||||
for i, tt := range tests {
|
||||
upserted := tt.res.upsert(tt.def, tt.packName, tt.fixStat)
|
||||
if tt.upserted != upserted {
|
||||
t.Errorf("[%d]\nexpected: %t\n actual: %t\n", i, tt.upserted, upserted)
|
||||
upsert := tt.res.upsert(tt.def, tt.packName, tt.fixStat)
|
||||
if tt.upsert != upsert {
|
||||
t.Errorf("[%d]\nexpected: %t\n actual: %t\n", i, tt.upsert, upsert)
|
||||
}
|
||||
if !reflect.DeepEqual(tt.out, tt.res) {
|
||||
t.Errorf("[%d]\nexpected: %v\n actual: %v\n", i, tt.out, tt.res)
|
||||
@@ -200,6 +202,7 @@ func TestIsOvalDefAffected(t *testing.T) {
|
||||
req request
|
||||
family string
|
||||
kernel models.Kernel
|
||||
mods []string
|
||||
}
|
||||
var tests = []struct {
|
||||
in in
|
||||
@@ -1074,9 +1077,85 @@ func TestIsOvalDefAffected(t *testing.T) {
|
||||
notFixedYet: false,
|
||||
fixedIn: "3.1.0",
|
||||
},
|
||||
// dnf module
|
||||
{
|
||||
in: in{
|
||||
family: config.RedHat,
|
||||
def: ovalmodels.Definition{
|
||||
AffectedPacks: []ovalmodels.Package{
|
||||
{
|
||||
Name: "nginx",
|
||||
Version: "1.16.1-1.module+el8.3.0+8844+e5e7039f.1",
|
||||
NotFixedYet: false,
|
||||
ModularityLabel: "nginx:1.16",
|
||||
},
|
||||
},
|
||||
},
|
||||
req: request{
|
||||
packName: "nginx",
|
||||
versionRelease: "1.16.0-1.module+el8.3.0+8844+e5e7039f.1",
|
||||
},
|
||||
mods: []string{
|
||||
"nginx:1.16",
|
||||
},
|
||||
},
|
||||
affected: true,
|
||||
notFixedYet: false,
|
||||
fixedIn: "1.16.1-1.module+el8.3.0+8844+e5e7039f.1",
|
||||
},
|
||||
// dnf module 2
|
||||
{
|
||||
in: in{
|
||||
family: config.RedHat,
|
||||
def: ovalmodels.Definition{
|
||||
AffectedPacks: []ovalmodels.Package{
|
||||
{
|
||||
Name: "nginx",
|
||||
Version: "1.16.1-1.module+el8.3.0+8844+e5e7039f.1",
|
||||
NotFixedYet: false,
|
||||
ModularityLabel: "nginx:1.16",
|
||||
},
|
||||
},
|
||||
},
|
||||
req: request{
|
||||
packName: "nginx",
|
||||
versionRelease: "1.16.2-1.module+el8.3.0+8844+e5e7039f.1",
|
||||
},
|
||||
mods: []string{
|
||||
"nginx:1.16",
|
||||
},
|
||||
},
|
||||
affected: false,
|
||||
notFixedYet: false,
|
||||
},
|
||||
// dnf module 3
|
||||
{
|
||||
in: in{
|
||||
family: config.RedHat,
|
||||
def: ovalmodels.Definition{
|
||||
AffectedPacks: []ovalmodels.Package{
|
||||
{
|
||||
Name: "nginx",
|
||||
Version: "1.16.1-1.module+el8.3.0+8844+e5e7039f.1",
|
||||
NotFixedYet: false,
|
||||
ModularityLabel: "nginx:1.16",
|
||||
},
|
||||
},
|
||||
},
|
||||
req: request{
|
||||
packName: "nginx",
|
||||
versionRelease: "1.16.0-1.module+el8.3.0+8844+e5e7039f.1",
|
||||
},
|
||||
mods: []string{
|
||||
"nginx:1.14",
|
||||
},
|
||||
},
|
||||
affected: false,
|
||||
notFixedYet: false,
|
||||
},
|
||||
}
|
||||
for i, tt := range tests {
|
||||
affected, notFixedYet, fixedIn := isOvalDefAffected(tt.in.def, tt.in.req, tt.in.family, tt.in.kernel)
|
||||
affected, notFixedYet, fixedIn := isOvalDefAffected(tt.in.def, tt.in.req, tt.in.family, tt.in.kernel, tt.in.mods)
|
||||
if tt.affected != affected {
|
||||
t.Errorf("[%d] affected\nexpected: %v\n actual: %v\n", i, tt.affected, affected)
|
||||
}
|
||||
@@ -1089,24 +1168,35 @@ func TestIsOvalDefAffected(t *testing.T) {
|
||||
}
|
||||
}
|
||||
|
||||
func Test_major(t *testing.T) {
|
||||
var tests = []struct {
|
||||
in string
|
||||
expected string
|
||||
func Test_centOSVersionToRHEL(t *testing.T) {
|
||||
type args struct {
|
||||
ver string
|
||||
}
|
||||
tests := []struct {
|
||||
name string
|
||||
args args
|
||||
want string
|
||||
}{
|
||||
{
|
||||
in: "4.1",
|
||||
expected: "4",
|
||||
name: "remove centos.",
|
||||
args: args{
|
||||
ver: "grub2-tools-2.02-0.80.el7.centos.x86_64",
|
||||
},
|
||||
want: "grub2-tools-2.02-0.80.el7.x86_64",
|
||||
},
|
||||
{
|
||||
in: "0:4.1",
|
||||
expected: "4",
|
||||
name: "noop",
|
||||
args: args{
|
||||
ver: "grub2-tools-2.02-0.80.el7.x86_64",
|
||||
},
|
||||
want: "grub2-tools-2.02-0.80.el7.x86_64",
|
||||
},
|
||||
}
|
||||
for i, tt := range tests {
|
||||
a := major(tt.in)
|
||||
if tt.expected != a {
|
||||
t.Errorf("[%d]\nexpected: %s\n actual: %s\n", i, tt.expected, a)
|
||||
}
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
if got := centOSVersionToRHEL(tt.args.ver); got != tt.want {
|
||||
t.Errorf("centOSVersionToRHEL() = %v, want %v", got, tt.want)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
@@ -3,7 +3,6 @@ package report
|
||||
import (
|
||||
"bytes"
|
||||
"encoding/json"
|
||||
"encoding/xml"
|
||||
"fmt"
|
||||
"time"
|
||||
|
||||
@@ -66,18 +65,6 @@ func (w AzureBlobWriter) Write(rs ...models.ScanResult) (err error) {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
if c.Conf.FormatXML {
|
||||
k := key + ".xml"
|
||||
var b []byte
|
||||
if b, err = xml.Marshal(r); err != nil {
|
||||
return xerrors.Errorf("Failed to Marshal to XML: %w", err)
|
||||
}
|
||||
allBytes := bytes.Join([][]byte{[]byte(xml.Header + vulsOpenTag), b, []byte(vulsCloseTag)}, []byte{})
|
||||
if err := createBlockBlob(cli, k, allBytes); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
@@ -1,3 +1,5 @@
|
||||
// +build !scanner
|
||||
|
||||
package report
|
||||
|
||||
import (
|
||||
@@ -16,7 +18,7 @@ import (
|
||||
cvemodels "github.com/kotakanbe/go-cve-dictionary/models"
|
||||
)
|
||||
|
||||
// CveClient is api client of CVE disctionary service.
|
||||
// CveClient is api client of CVE dictionary service.
|
||||
var CveClient cvedictClient
|
||||
|
||||
type cvedictClient struct {
|
||||
@@ -24,29 +26,6 @@ type cvedictClient struct {
|
||||
baseURL string
|
||||
}
|
||||
|
||||
func (api *cvedictClient) initialize() {
|
||||
api.baseURL = config.Conf.CveDict.URL
|
||||
}
|
||||
|
||||
func (api cvedictClient) CheckHealth() error {
|
||||
if !config.Conf.CveDict.IsFetchViaHTTP() {
|
||||
util.Log.Debugf("get cve-dictionary from %s", config.Conf.CveDict.Type)
|
||||
return nil
|
||||
}
|
||||
|
||||
api.initialize()
|
||||
url := fmt.Sprintf("%s/health", api.baseURL)
|
||||
var errs []error
|
||||
var resp *http.Response
|
||||
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 xerrors.Errorf("Failed to request to CVE server. url: %s, errs: %w",
|
||||
url, errs)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
type response struct {
|
||||
Key string
|
||||
CveDetail cvemodels.CveDetail
|
||||
@@ -137,7 +116,7 @@ func (api cvedictClient) httpGet(key, url string, resChan chan<- response, errCh
|
||||
// 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 {
|
||||
return xerrors.Errorf("HTTP GET Error, url: %s, resp: %v, err: %w",
|
||||
return xerrors.Errorf("HTTP GET Error, url: %s, resp: %v, err: %s",
|
||||
url, resp, errs)
|
||||
}
|
||||
return nil
|
||||
@@ -153,7 +132,7 @@ func (api cvedictClient) httpGet(key, url string, resChan chan<- response, errCh
|
||||
}
|
||||
cveDetail := cvemodels.CveDetail{}
|
||||
if err := json.Unmarshal([]byte(body), &cveDetail); err != nil {
|
||||
errChan <- xerrors.Errorf("Failed to Unmarshall. body: %s, err: %w", body, err)
|
||||
errChan <- xerrors.Errorf("Failed to Unmarshal. body: %s, err: %w", body, err)
|
||||
return
|
||||
}
|
||||
resChan <- response{
|
||||
@@ -189,7 +168,7 @@ func (api cvedictClient) httpPost(key, url string, query map[string]string) ([]c
|
||||
}
|
||||
resp, body, errs = req.End()
|
||||
if 0 < len(errs) || resp == nil || resp.StatusCode != 200 {
|
||||
return xerrors.Errorf("HTTP POST error. url: %s, resp: %v, err: %w", url, resp, errs)
|
||||
return xerrors.Errorf("HTTP POST error. url: %s, resp: %v, err: %s", url, resp, errs)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
@@ -204,7 +183,7 @@ func (api cvedictClient) httpPost(key, url string, query map[string]string) ([]c
|
||||
cveDetails := []cvemodels.CveDetail{}
|
||||
if err := json.Unmarshal([]byte(body), &cveDetails); err != nil {
|
||||
return nil,
|
||||
xerrors.Errorf("Failed to Unmarshall. body: %s, err: %w", body, err)
|
||||
xerrors.Errorf("Failed to Unmarshal. body: %s, err: %w", body, err)
|
||||
}
|
||||
return cveDetails, nil
|
||||
}
|
||||
|
||||
@@ -1,3 +1,5 @@
|
||||
// +build !scanner
|
||||
|
||||
package report
|
||||
|
||||
import (
|
||||
@@ -8,12 +10,12 @@ import (
|
||||
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"
|
||||
metasploitdb "github.com/takuzoo3868/go-msfdb/db"
|
||||
exploitdb "github.com/vulsio/go-exploitdb/db"
|
||||
"golang.org/x/xerrors"
|
||||
)
|
||||
|
||||
// DBClient is a dictionarie's db client for reporting
|
||||
// DBClient is DB client for reporting
|
||||
type DBClient struct {
|
||||
CveDB cvedb.DB
|
||||
OvalDB ovaldb.DB
|
||||
@@ -174,7 +176,7 @@ func NewExploitDB(cnf DBClientConf) (driver exploitdb.DB, locked bool, err error
|
||||
path = cnf.ExploitCnf.SQLite3Path
|
||||
|
||||
if _, err := os.Stat(path); os.IsNotExist(err) {
|
||||
util.Log.Warnf("--exploitdb-path=%s file not found. Fetch go-exploit-db before reporting if you want to display exploit codes of detected CVE-IDs. For details, see `https://github.com/mozqnet/go-exploitdb`", path)
|
||||
util.Log.Warnf("--exploitdb-path=%s file not found. Fetch go-exploit-db before reporting if you want to display exploit codes of detected CVE-IDs. For details, see `https://github.com/vulsio/go-exploitdb`", path)
|
||||
return nil, false, nil
|
||||
}
|
||||
}
|
||||
|
||||
@@ -123,11 +123,11 @@ func (e *emailSender) sendMail(smtpServerAddr, message string) (err error) {
|
||||
if ok, param := c.Extension("AUTH"); ok {
|
||||
authList := strings.Split(param, " ")
|
||||
auth = e.newSaslClient(authList)
|
||||
if err = c.Auth(auth); err != nil {
|
||||
return xerrors.Errorf("Failed to authenticate: %w", err)
|
||||
}
|
||||
}
|
||||
|
||||
if err = c.Auth(auth); err != nil {
|
||||
return xerrors.Errorf("Failed to authenticate: %w", err)
|
||||
}
|
||||
if err = c.Mail(emailConf.From, nil); err != nil {
|
||||
return xerrors.Errorf("Failed to send Mail command: %w", err)
|
||||
}
|
||||
|
||||
@@ -1,74 +0,0 @@
|
||||
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
|
||||
}
|
||||
@@ -37,7 +37,7 @@ type HTTPResponseWriter struct {
|
||||
func (w HTTPResponseWriter) Write(rs ...models.ScanResult) (err error) {
|
||||
res, err := json.Marshal(rs)
|
||||
if err != nil {
|
||||
return xerrors.Errorf("Failed to marshal scah results: %w", err)
|
||||
return xerrors.Errorf("Failed to marshal scan results: %w", err)
|
||||
}
|
||||
w.Writer.Header().Set("Content-Type", "application/json")
|
||||
_, err = w.Writer.Write(res)
|
||||
|
||||
@@ -1,9 +1,7 @@
|
||||
package report
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"encoding/json"
|
||||
"encoding/xml"
|
||||
"io/ioutil"
|
||||
"os"
|
||||
"path/filepath"
|
||||
@@ -79,24 +77,6 @@ func (w LocalFileWriter) Write(rs ...models.ScanResult) (err error) {
|
||||
}
|
||||
}
|
||||
|
||||
if c.Conf.FormatXML {
|
||||
var p string
|
||||
if c.Conf.Diff {
|
||||
p = path + "_diff.xml"
|
||||
} else {
|
||||
p = path + ".xml"
|
||||
}
|
||||
|
||||
var b []byte
|
||||
if b, err = xml.Marshal(r); err != nil {
|
||||
return xerrors.Errorf("Failed to Marshal to XML: %w", err)
|
||||
}
|
||||
allBytes := bytes.Join([][]byte{[]byte(xml.Header + vulsOpenTag), b, []byte(vulsCloseTag)}, []byte{})
|
||||
if err := writeFile(p, allBytes, 0600); err != nil {
|
||||
return xerrors.Errorf("Failed to write XML. path: %s, err: %w", p, err)
|
||||
}
|
||||
}
|
||||
|
||||
if c.Conf.FormatCsvList {
|
||||
p := path + "_short.csv"
|
||||
if c.Conf.Diff {
|
||||
|
||||
569
report/report.go
569
report/report.go
@@ -1,19 +1,12 @@
|
||||
// +build !scanner
|
||||
|
||||
package report
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"fmt"
|
||||
"io/ioutil"
|
||||
"os"
|
||||
"reflect"
|
||||
"regexp"
|
||||
"sort"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"github.com/future-architect/vuls/libmanager"
|
||||
|
||||
"github.com/BurntSushi/toml"
|
||||
"github.com/future-architect/vuls/config"
|
||||
c "github.com/future-architect/vuls/config"
|
||||
"github.com/future-architect/vuls/contrib/owasp-dependency-check/parser"
|
||||
@@ -21,26 +14,21 @@ import (
|
||||
"github.com/future-architect/vuls/exploit"
|
||||
"github.com/future-architect/vuls/github"
|
||||
"github.com/future-architect/vuls/gost"
|
||||
"github.com/future-architect/vuls/libmanager"
|
||||
"github.com/future-architect/vuls/models"
|
||||
"github.com/future-architect/vuls/msf"
|
||||
"github.com/future-architect/vuls/oval"
|
||||
"github.com/future-architect/vuls/util"
|
||||
"github.com/future-architect/vuls/wordpress"
|
||||
"github.com/hashicorp/go-uuid"
|
||||
gostdb "github.com/knqyf263/gost/db"
|
||||
cvedb "github.com/kotakanbe/go-cve-dictionary/db"
|
||||
cvemodels "github.com/kotakanbe/go-cve-dictionary/models"
|
||||
ovaldb "github.com/kotakanbe/goval-dictionary/db"
|
||||
exploitdb "github.com/mozqnet/go-exploitdb/db"
|
||||
metasploitdb "github.com/takuzoo3868/go-msfdb/db"
|
||||
exploitdb "github.com/vulsio/go-exploitdb/db"
|
||||
"golang.org/x/xerrors"
|
||||
)
|
||||
|
||||
const (
|
||||
vulsOpenTag = "<vulsreport>"
|
||||
vulsCloseTag = "</vulsreport>"
|
||||
)
|
||||
|
||||
// FillCveInfos fills CVE Detailed Information
|
||||
func FillCveInfos(dbclient DBClient, rs []models.ScanResult, dir string) ([]models.ScanResult, error) {
|
||||
|
||||
@@ -52,7 +40,7 @@ func FillCveInfos(dbclient DBClient, rs []models.ScanResult, dir string) ([]mode
|
||||
continue
|
||||
}
|
||||
|
||||
if !useScannedCves(&r) {
|
||||
if !reuseScannedCves(&r) {
|
||||
r.ScannedCves = models.VulnInfos{}
|
||||
}
|
||||
|
||||
@@ -86,25 +74,28 @@ func FillCveInfos(dbclient DBClient, rs []models.ScanResult, dir string) ([]mode
|
||||
}
|
||||
}
|
||||
|
||||
nCVEs, err := libmanager.DetectLibsCves(&r)
|
||||
if err != nil {
|
||||
if err := libmanager.DetectLibsCves(&r); err != nil {
|
||||
return nil, xerrors.Errorf("Failed to fill with Library dependency: %w", err)
|
||||
}
|
||||
util.Log.Infof("%s: %d CVEs are detected with Library",
|
||||
r.FormatServerName(), nCVEs)
|
||||
|
||||
// Integrations
|
||||
githubInts := GithubSecurityAlerts(c.Conf.Servers[r.ServerName].GitHubRepos)
|
||||
if err := DetectPkgCves(dbclient, &r); err != nil {
|
||||
return nil, xerrors.Errorf("Failed to detect Pkg CVE: %w", err)
|
||||
}
|
||||
|
||||
wpVulnCaches := map[string]string{}
|
||||
wpOpt := WordPressOption{c.Conf.Servers[r.ServerName].WordPress.WPVulnDBToken, &wpVulnCaches}
|
||||
if err := DetectCpeURIsCves(dbclient.CveDB, &r, cpeURIs); err != nil {
|
||||
return nil, xerrors.Errorf("Failed to detect CVE of `%s`: %w", cpeURIs, err)
|
||||
}
|
||||
|
||||
if err := FillCveInfo(dbclient,
|
||||
&r,
|
||||
cpeURIs,
|
||||
true,
|
||||
githubInts,
|
||||
wpOpt); err != nil {
|
||||
repos := c.Conf.Servers[r.ServerName].GitHubRepos
|
||||
if err := DetectGitHubCves(&r, repos); err != nil {
|
||||
return nil, xerrors.Errorf("Failed to detect GitHub Cves: %w", err)
|
||||
}
|
||||
|
||||
if err := DetectWordPressCves(&r, &config.Conf.WpScan); err != nil {
|
||||
return nil, xerrors.Errorf("Failed to detect WordPress Cves: %w", err)
|
||||
}
|
||||
|
||||
if err := FillCveInfo(dbclient, &r); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
@@ -151,9 +142,9 @@ func FillCveInfos(dbclient DBClient, rs []models.ScanResult, dir string) ([]mode
|
||||
for i, r := range rs {
|
||||
r = r.FilterByCvssOver(c.Conf.CvssScoreOver)
|
||||
r = r.FilterIgnoreCves()
|
||||
r = r.FilterUnfixed()
|
||||
r = r.FilterUnfixed(c.Conf.IgnoreUnfixed)
|
||||
r = r.FilterIgnorePkgs()
|
||||
r = r.FilterInactiveWordPressLibs()
|
||||
r = r.FilterInactiveWordPressLibs(c.Conf.WpScan.DetectInactive)
|
||||
if c.Conf.IgnoreUnscoredCves {
|
||||
r.ScannedCves = r.ScannedCves.FindScoredVulns()
|
||||
}
|
||||
@@ -162,15 +153,26 @@ func FillCveInfos(dbclient DBClient, rs []models.ScanResult, dir string) ([]mode
|
||||
return rs, nil
|
||||
}
|
||||
|
||||
// FillCveInfo fill scanResult with cve info.
|
||||
func FillCveInfo(dbclient DBClient, r *models.ScanResult, cpeURIs []string, ignoreWillNotFix bool, integrations ...Integration) error {
|
||||
util.Log.Debugf("need to refresh")
|
||||
nCVEs, err := DetectPkgsCvesWithOval(dbclient.OvalDB, r)
|
||||
if err != nil {
|
||||
return xerrors.Errorf("Failed to fill with OVAL: %w", err)
|
||||
// DetectPkgCves detects OS pkg cves
|
||||
func DetectPkgCves(dbclient DBClient, r *models.ScanResult) error {
|
||||
// Pkg Scan
|
||||
if r.Release != "" {
|
||||
// OVAL
|
||||
if err := detectPkgsCvesWithOval(dbclient.OvalDB, r); err != nil {
|
||||
return xerrors.Errorf("Failed to detect CVE with OVAL: %w", err)
|
||||
}
|
||||
|
||||
// gost
|
||||
if err := detectPkgsCvesWithGost(dbclient.GostDB, r); err != nil {
|
||||
return xerrors.Errorf("Failed to detect CVE with gost: %w", err)
|
||||
}
|
||||
} else if reuseScannedCves(r) {
|
||||
util.Log.Infof("r.Release is empty. Use CVEs as it as.")
|
||||
} else if r.Family == c.ServerTypePseudo {
|
||||
util.Log.Infof("pseudo type. Skip OVAL and gost detection")
|
||||
} else {
|
||||
return xerrors.Errorf("Failed to fill CVEs. r.Release is empty")
|
||||
}
|
||||
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 {
|
||||
@@ -181,53 +183,97 @@ func FillCveInfo(dbclient DBClient, r *models.ScanResult, cpeURIs []string, igno
|
||||
}
|
||||
}
|
||||
|
||||
nCVEs, err = DetectCpeURIsCves(dbclient.CveDB, r, cpeURIs)
|
||||
if err != nil {
|
||||
return xerrors.Errorf("Failed to detect vulns of `%s`: %w", cpeURIs, err)
|
||||
}
|
||||
util.Log.Infof("%s: %d CVEs are detected with CPE", r.FormatServerName(), nCVEs)
|
||||
|
||||
ints := &integrationResults{}
|
||||
for _, o := range integrations {
|
||||
if err = o.apply(r, ints); err != nil {
|
||||
return xerrors.Errorf("Failed to fill with integration: %w", err)
|
||||
// To keep backward compatibility
|
||||
// Newer versions use ListenPortStats,
|
||||
// but older versions of Vuls are set to ListenPorts.
|
||||
// Set ListenPorts to ListenPortStats to allow newer Vuls to report old results.
|
||||
for i, pkg := range r.Packages {
|
||||
for j, proc := range pkg.AffectedProcs {
|
||||
for _, ipPort := range proc.ListenPorts {
|
||||
ps, err := models.NewPortStat(ipPort)
|
||||
if err != nil {
|
||||
util.Log.Warnf("Failed to parse ip:port: %s, err:%+v", ipPort, err)
|
||||
continue
|
||||
}
|
||||
r.Packages[i].AffectedProcs[j].ListenPortStats = append(
|
||||
r.Packages[i].AffectedProcs[j].ListenPortStats, *ps)
|
||||
}
|
||||
}
|
||||
}
|
||||
util.Log.Infof("%s: %d CVEs are detected with GitHub Security Alerts", r.FormatServerName(), ints.GithubAlertsCveCounts)
|
||||
|
||||
nCVEs, err = DetectPkgsCvesWithGost(dbclient.GostDB, r, ignoreWillNotFix)
|
||||
return nil
|
||||
}
|
||||
|
||||
// DetectGitHubCves fetches CVEs from GitHub Security Alerts
|
||||
func DetectGitHubCves(r *models.ScanResult, githubConfs map[string]c.GitHubConf) error {
|
||||
if len(githubConfs) == 0 {
|
||||
return nil
|
||||
}
|
||||
for ownerRepo, setting := range githubConfs {
|
||||
ss := strings.Split(ownerRepo, "/")
|
||||
if len(ss) != 2 {
|
||||
return xerrors.Errorf("Failed to parse GitHub owner/repo: %s", ownerRepo)
|
||||
}
|
||||
owner, repo := ss[0], ss[1]
|
||||
n, err := github.DetectGitHubSecurityAlerts(r, owner, repo, setting.Token)
|
||||
if err != nil {
|
||||
return xerrors.Errorf("Failed to access GitHub Security Alerts: %w", err)
|
||||
}
|
||||
util.Log.Infof("%s: %d CVEs detected with GHSA %s/%s",
|
||||
r.FormatServerName(), n, owner, repo)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// DetectWordPressCves detects CVEs of WordPress
|
||||
func DetectWordPressCves(r *models.ScanResult, wpCnf *c.WpScanConf) error {
|
||||
if len(r.WordPressPackages) == 0 {
|
||||
return nil
|
||||
}
|
||||
util.Log.Infof("Detect WordPress CVE. pkgs: %d ", len(r.WordPressPackages))
|
||||
n, err := wordpress.DetectWordPressCves(r, wpCnf)
|
||||
if err != nil {
|
||||
return xerrors.Errorf("Failed to detect WordPress CVE: %w", err)
|
||||
}
|
||||
util.Log.Infof("%s: found %d WordPress CVEs", r.FormatServerName(), n)
|
||||
return nil
|
||||
}
|
||||
|
||||
// FillCveInfo fill scanResult with cve info.
|
||||
func FillCveInfo(dbclient DBClient, r *models.ScanResult) error {
|
||||
util.Log.Infof("Fill CVE detailed with gost")
|
||||
if err := gost.NewClient(r.Family).FillCVEsWithRedHat(dbclient.GostDB, r); err != nil {
|
||||
return xerrors.Errorf("Failed to fill with gost: %w", 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")
|
||||
util.Log.Infof("Fill CVE detailed with CVE-DB")
|
||||
if err := fillCvesWithNvdJvn(dbclient.CveDB, r); err != nil {
|
||||
return xerrors.Errorf("Failed to fill with CVE: %w", err)
|
||||
}
|
||||
|
||||
util.Log.Infof("Fill exploit information with Exploit-DB")
|
||||
nExploitCve, err := FillWithExploitDB(dbclient.ExploitDB, r)
|
||||
util.Log.Infof("Fill exploit with Exploit-DB")
|
||||
nExploitCve, err := fillWithExploitDB(dbclient.ExploitDB, r)
|
||||
if err != nil {
|
||||
return xerrors.Errorf("Failed to fill with exploit: %w", err)
|
||||
}
|
||||
util.Log.Infof("%s: %d exploits are detected",
|
||||
r.FormatServerName(), nExploitCve)
|
||||
|
||||
util.Log.Infof("Fill metasploit module information with Metasploit-DB")
|
||||
nMetasploitCve, err := FillWithMetasploit(dbclient.MetasploitDB, r)
|
||||
util.Log.Infof("Fill metasploit module with Metasploit-DB")
|
||||
nMetasploitCve, err := fillWithMetasploit(dbclient.MetasploitDB, r)
|
||||
if err != nil {
|
||||
return xerrors.Errorf("Failed to fill with metasploit: %w", err)
|
||||
}
|
||||
util.Log.Infof("%s: %d modules are detected",
|
||||
r.FormatServerName(), nMetasploitCve)
|
||||
|
||||
util.Log.Infof("Fill CWE with NVD")
|
||||
fillCweDict(r)
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// fillCvesWithNvdJvn fetches NVD, JVN from CVE Database
|
||||
// fillCvesWithNvdJvn fills CVE detail with NVD, JVN
|
||||
func fillCvesWithNvdJvn(driver cvedb.DB, r *models.ScanResult) error {
|
||||
cveIDs := []string{}
|
||||
for _, v := range r.ScannedCves {
|
||||
@@ -239,7 +285,7 @@ func fillCvesWithNvdJvn(driver cvedb.DB, r *models.ScanResult) error {
|
||||
return err
|
||||
}
|
||||
for _, d := range ds {
|
||||
nvd := models.ConvertNvdJSONToModel(d.CveID, d.NvdJSON)
|
||||
nvd, exploits, mitigations := models.ConvertNvdJSONToModel(d.CveID, d.NvdJSON)
|
||||
jvn := models.ConvertJvnToModel(d.CveID, d.Jvn)
|
||||
|
||||
alerts := fillCertAlerts(&d)
|
||||
@@ -254,6 +300,8 @@ func fillCvesWithNvdJvn(driver cvedb.DB, r *models.ScanResult) error {
|
||||
}
|
||||
}
|
||||
vinfo.AlertDict = alerts
|
||||
vinfo.Exploits = append(vinfo.Exploits, exploits...)
|
||||
vinfo.Mitigations = append(vinfo.Mitigations, mitigations...)
|
||||
r.ScannedCves[cveID] = vinfo
|
||||
break
|
||||
}
|
||||
@@ -284,8 +332,8 @@ func fillCertAlerts(cvedetail *cvemodels.CveDetail) (dict models.AlertDict) {
|
||||
return dict
|
||||
}
|
||||
|
||||
// DetectPkgsCvesWithOval fetches OVAL database
|
||||
func DetectPkgsCvesWithOval(driver ovaldb.DB, r *models.ScanResult) (nCVEs int, err error) {
|
||||
// detectPkgsCvesWithOval fetches OVAL database
|
||||
func detectPkgsCvesWithOval(driver ovaldb.DB, r *models.ScanResult) error {
|
||||
var ovalClient oval.Client
|
||||
var ovalFamily string
|
||||
|
||||
@@ -317,78 +365,80 @@ func DetectPkgsCvesWithOval(driver ovaldb.DB, r *models.ScanResult) (nCVEs int,
|
||||
ovalClient = oval.NewAmazon()
|
||||
ovalFamily = c.Amazon
|
||||
case c.FreeBSD, c.Windows:
|
||||
return 0, nil
|
||||
return nil
|
||||
case c.ServerTypePseudo:
|
||||
return 0, nil
|
||||
return nil
|
||||
default:
|
||||
if r.Family == "" {
|
||||
return 0, xerrors.New("Probably an error occurred during scanning. Check the error message")
|
||||
return xerrors.New("Probably an error occurred during scanning. Check the error message")
|
||||
}
|
||||
return 0, xerrors.Errorf("OVAL for %s is not implemented yet", r.Family)
|
||||
return xerrors.Errorf("OVAL for %s is not implemented yet", r.Family)
|
||||
}
|
||||
|
||||
if !c.Conf.OvalDict.IsFetchViaHTTP() {
|
||||
if driver == nil {
|
||||
return 0, xerrors.Errorf("You have to fetch OVAL data for %s before reporting. For details, see `https://github.com/kotakanbe/goval-dictionary#usage`", r.Family)
|
||||
return xerrors.Errorf("You have to fetch OVAL data for %s before reporting. For details, see `https://github.com/kotakanbe/goval-dictionary#usage`", r.Family)
|
||||
}
|
||||
if err = driver.NewOvalDB(ovalFamily); err != nil {
|
||||
return 0, xerrors.Errorf("Failed to New Oval DB. err: %w", err)
|
||||
if err := driver.NewOvalDB(ovalFamily); err != nil {
|
||||
return xerrors.Errorf("Failed to New Oval DB. err: %w", 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 0, err
|
||||
return err
|
||||
}
|
||||
if !ok {
|
||||
return 0, xerrors.Errorf("OVAL entries of %s %s are not found. Fetch OVAL before reporting. For details, see `https://github.com/kotakanbe/goval-dictionary#usage`", ovalFamily, r.Release)
|
||||
return xerrors.Errorf("OVAL entries of %s %s are not found. Fetch OVAL before reporting. For details, see `https://github.com/kotakanbe/goval-dictionary#usage`", ovalFamily, r.Release)
|
||||
}
|
||||
|
||||
_, err = ovalClient.CheckIfOvalFresh(driver, ovalFamily, r.Release)
|
||||
if err != nil {
|
||||
return 0, err
|
||||
return err
|
||||
}
|
||||
|
||||
return ovalClient.FillWithOval(driver, r)
|
||||
}
|
||||
|
||||
// DetectPkgsCvesWithGost fills CVEs with gost dataabase
|
||||
// https://github.com/knqyf263/gost
|
||||
func DetectPkgsCvesWithGost(driver gostdb.DB, r *models.ScanResult, ignoreWillNotFix bool) (nCVEs int, err error) {
|
||||
gostClient := gost.NewClient(r.Family)
|
||||
// TODO check if fetched
|
||||
// TODO check if fresh enough
|
||||
if nCVEs, err = gostClient.DetectUnfixed(driver, r, ignoreWillNotFix); err != nil {
|
||||
return
|
||||
nCVEs, err := ovalClient.FillWithOval(driver, r)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
return nCVEs, gostClient.FillCVEsWithRedHat(driver, r)
|
||||
|
||||
util.Log.Infof("%s: %d CVEs are detected with OVAL", r.FormatServerName(), nCVEs)
|
||||
return nil
|
||||
}
|
||||
|
||||
// FillWithExploitDB fills Exploits with exploit dataabase
|
||||
// https://github.com/mozqnet/go-exploitdb
|
||||
func FillWithExploitDB(driver exploitdb.DB, r *models.ScanResult) (nExploitCve int, err error) {
|
||||
// TODO check if fetched
|
||||
// TODO check if fresh enough
|
||||
return exploit.FillWithExploit(driver, r)
|
||||
func detectPkgsCvesWithGost(driver gostdb.DB, r *models.ScanResult) error {
|
||||
nCVEs, err := gost.NewClient(r.Family).DetectUnfixed(driver, r, true)
|
||||
|
||||
util.Log.Infof("%s: %d unfixed CVEs are detected with gost",
|
||||
r.FormatServerName(), nCVEs)
|
||||
return err
|
||||
}
|
||||
|
||||
// FillWithMetasploit fills metasploit modules with metasploit database
|
||||
// fillWithExploitDB fills Exploits with exploit dataabase
|
||||
// https://github.com/vulsio/go-exploitdb
|
||||
func fillWithExploitDB(driver exploitdb.DB, r *models.ScanResult) (nExploitCve int, err error) {
|
||||
return exploit.FillWithExploit(driver, r, &config.Conf.Exploit)
|
||||
}
|
||||
|
||||
// fillWithMetasploit fills metasploit modules with metasploit database
|
||||
// https://github.com/takuzoo3868/go-msfdb
|
||||
func FillWithMetasploit(driver metasploitdb.DB, r *models.ScanResult) (nMetasploitCve int, err error) {
|
||||
func fillWithMetasploit(driver metasploitdb.DB, r *models.ScanResult) (nMetasploitCve int, err error) {
|
||||
return msf.FillWithMetasploit(driver, r)
|
||||
}
|
||||
|
||||
func DetectCpeURIsCves(driver cvedb.DB, r *models.ScanResult, cpeURIs []string) (nCVEs int, err error) {
|
||||
if len(cpeURIs) != 0 && driver == nil && !config.Conf.CveDict.IsFetchViaHTTP() {
|
||||
return 0, xerrors.Errorf("cpeURIs %s specified, but cve-dictionary DB not found. Fetch cve-dictionary before reporting. For details, see `https://github.com/kotakanbe/go-cve-dictionary#deploy-go-cve-dictionary`",
|
||||
// DetectCpeURIsCves detects CVEs of given CPE-URIs
|
||||
func DetectCpeURIsCves(driver cvedb.DB, r *models.ScanResult, cpeURIs []string) error {
|
||||
nCVEs := 0
|
||||
if len(cpeURIs) != 0 && driver == nil && !c.Conf.CveDict.IsFetchViaHTTP() {
|
||||
return xerrors.Errorf("cpeURIs %s specified, but cve-dictionary DB not found. Fetch cve-dictionary before reporting. For details, see `https://github.com/kotakanbe/go-cve-dictionary#deploy-go-cve-dictionary`",
|
||||
cpeURIs)
|
||||
}
|
||||
|
||||
for _, name := range cpeURIs {
|
||||
details, err := CveClient.FetchCveDetailsByCpeName(driver, name)
|
||||
if err != nil {
|
||||
return 0, err
|
||||
return err
|
||||
}
|
||||
for _, detail := range details {
|
||||
if val, ok := r.ScannedCves[detail.CveID]; ok {
|
||||
@@ -408,62 +458,7 @@ func DetectCpeURIsCves(driver cvedb.DB, r *models.ScanResult, cpeURIs []string)
|
||||
}
|
||||
}
|
||||
}
|
||||
return nCVEs, nil
|
||||
}
|
||||
|
||||
type integrationResults struct {
|
||||
GithubAlertsCveCounts int
|
||||
WordPressCveCounts int
|
||||
}
|
||||
|
||||
// Integration is integration of vuls report
|
||||
type Integration interface {
|
||||
apply(*models.ScanResult, *integrationResults) error
|
||||
}
|
||||
|
||||
// GithubSecurityAlerts :
|
||||
func GithubSecurityAlerts(githubConfs map[string]config.GitHubConf) Integration {
|
||||
return GithubSecurityAlertOption{
|
||||
GithubConfs: githubConfs,
|
||||
}
|
||||
}
|
||||
|
||||
// GithubSecurityAlertOption :
|
||||
type GithubSecurityAlertOption struct {
|
||||
GithubConfs map[string]config.GitHubConf
|
||||
}
|
||||
|
||||
// https://help.github.com/articles/about-security-alerts-for-vulnerable-dependencies/
|
||||
func (g GithubSecurityAlertOption) apply(r *models.ScanResult, ints *integrationResults) (err error) {
|
||||
var nCVEs int
|
||||
for ownerRepo, setting := range g.GithubConfs {
|
||||
ss := strings.Split(ownerRepo, "/")
|
||||
owner, repo := ss[0], ss[1]
|
||||
n, err := github.FillGitHubSecurityAlerts(r, owner, repo, setting.Token)
|
||||
if err != nil {
|
||||
return xerrors.Errorf("Failed to access GitHub Security Alerts: %w", err)
|
||||
}
|
||||
nCVEs += n
|
||||
}
|
||||
ints.GithubAlertsCveCounts = nCVEs
|
||||
return nil
|
||||
}
|
||||
|
||||
// WordPressOption :
|
||||
type WordPressOption struct {
|
||||
token string
|
||||
wpVulnCaches *map[string]string
|
||||
}
|
||||
|
||||
func (g WordPressOption) apply(r *models.ScanResult, ints *integrationResults) (err error) {
|
||||
if g.token == "" {
|
||||
return nil
|
||||
}
|
||||
n, err := wordpress.FillWordPress(r, g.token, g.wpVulnCaches)
|
||||
if err != nil {
|
||||
return xerrors.Errorf("Failed to fetch from WPVulnDB. Check the WPVulnDBToken in config.toml. err: %w", err)
|
||||
}
|
||||
ints.WordPressCveCounts = n
|
||||
util.Log.Infof("%s: %d CVEs are detected with CPE", r.FormatServerName(), nCVEs)
|
||||
return nil
|
||||
}
|
||||
|
||||
@@ -521,269 +516,3 @@ func fillCweDict(r *models.ScanResult) {
|
||||
r.CweDict = dict
|
||||
return
|
||||
}
|
||||
|
||||
const reUUID = "[\\da-f]{8}-[\\da-f]{4}-[\\da-f]{4}-[\\da-f]{4}-[\\da-f]{12}"
|
||||
|
||||
// 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.
|
||||
func getOrCreateServerUUID(r models.ScanResult, server c.ServerInfo) (serverUUID string, err error) {
|
||||
if id, ok := server.UUIDs[r.ServerName]; !ok {
|
||||
if serverUUID, err = uuid.GenerateUUID(); err != nil {
|
||||
return "", xerrors.Errorf("Failed to generate UUID: %w", err)
|
||||
}
|
||||
} else {
|
||||
matched, err := regexp.MatchString(reUUID, id)
|
||||
if !matched || err != nil {
|
||||
if serverUUID, err = uuid.GenerateUUID(); err != nil {
|
||||
return "", xerrors.Errorf("Failed to generate UUID: %w", err)
|
||||
}
|
||||
}
|
||||
}
|
||||
return serverUUID, nil
|
||||
}
|
||||
|
||||
// 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) (err 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
|
||||
})
|
||||
|
||||
re := regexp.MustCompile(reUUID)
|
||||
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)
|
||||
serverUUID, err := getOrCreateServerUUID(r, server)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if serverUUID != "" {
|
||||
server.UUIDs[r.ServerName] = serverUUID
|
||||
}
|
||||
} else {
|
||||
name = r.ServerName
|
||||
}
|
||||
|
||||
if id, ok := server.UUIDs[name]; ok {
|
||||
ok := re.MatchString(id)
|
||||
if !ok || 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
|
||||
serverUUID, err := uuid.GenerateUUID()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
server.UUIDs[name] = serverUUID
|
||||
server = cleanForTOMLEncoding(server, c.Conf.Default)
|
||||
c.Conf.Servers[r.ServerName] = server
|
||||
|
||||
if r.IsContainer() {
|
||||
results[i].Container.UUID = serverUUID
|
||||
results[i].ServerUUID = server.UUIDs[r.ServerName]
|
||||
} else {
|
||||
results[i].ServerUUID = serverUUID
|
||||
}
|
||||
}
|
||||
|
||||
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
|
||||
metasploit := &c.Conf.Metasploit
|
||||
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"`
|
||||
Metasploit *c.MetasploitConf `toml:"metasploit"`
|
||||
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,
|
||||
Metasploit: metasploit,
|
||||
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 xerrors.Errorf("Failed to lstat %s: %w", configPath, err)
|
||||
}
|
||||
realPath := configPath
|
||||
if info.Mode()&os.ModeSymlink == os.ModeSymlink {
|
||||
if realPath, err = os.Readlink(configPath); err != nil {
|
||||
return xerrors.Errorf("Failed to Read link %s: %w", configPath, err)
|
||||
}
|
||||
}
|
||||
if err := os.Rename(realPath, realPath+".bak"); err != nil {
|
||||
return xerrors.Errorf("Failed to rename %s: %w", configPath, err)
|
||||
}
|
||||
|
||||
var buf bytes.Buffer
|
||||
if err := toml.NewEncoder(&buf).Encode(c); err != nil {
|
||||
return xerrors.Errorf("Failed to encode to toml: %w", err)
|
||||
}
|
||||
str := strings.Replace(buf.String(), "\n [", "\n\n [", -1)
|
||||
str = fmt.Sprintf("%s\n\n%s",
|
||||
"# See README 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
|
||||
}
|
||||
|
||||
13
report/s3.go
13
report/s3.go
@@ -3,7 +3,6 @@ package report
|
||||
import (
|
||||
"bytes"
|
||||
"encoding/json"
|
||||
"encoding/xml"
|
||||
"fmt"
|
||||
"path"
|
||||
"time"
|
||||
@@ -92,18 +91,6 @@ func (w S3Writer) Write(rs ...models.ScanResult) (err error) {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
if c.Conf.FormatXML {
|
||||
k := key + ".xml"
|
||||
var b []byte
|
||||
if b, err = xml.Marshal(r); err != nil {
|
||||
return xerrors.Errorf("Failed to Marshal to XML: %w", err)
|
||||
}
|
||||
allBytes := bytes.Join([][]byte{[]byte(xml.Header + vulsOpenTag), b, []byte(vulsCloseTag)}, []byte{})
|
||||
if err := putObject(svc, k, allBytes); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
@@ -143,7 +143,7 @@ func send(msg message) error {
|
||||
return nil
|
||||
}
|
||||
return xerrors.Errorf(
|
||||
"HTTP POST error. url: %s, resp: %v, body: %s, err: %w",
|
||||
"HTTP POST error. url: %s, resp: %v, body: %s, err: %s",
|
||||
conf.HookURL, resp, body, errs)
|
||||
}
|
||||
return nil
|
||||
@@ -248,7 +248,7 @@ func attachmentText(vinfo models.VulnInfo, osFamily string, cweDict map[string]m
|
||||
maxCvss := vinfo.MaxCvssScore()
|
||||
vectors := []string{}
|
||||
|
||||
scores := append(vinfo.Cvss3Scores(), vinfo.Cvss2Scores(osFamily)...)
|
||||
scores := append(vinfo.Cvss3Scores(), vinfo.Cvss2Scores()...)
|
||||
for _, cvss := range scores {
|
||||
if cvss.Value.Severity == "" {
|
||||
continue
|
||||
@@ -277,9 +277,8 @@ func attachmentText(vinfo models.VulnInfo, osFamily string, cweDict map[string]m
|
||||
} else {
|
||||
if 0 < len(vinfo.DistroAdvisories) {
|
||||
links := []string{}
|
||||
for k, v := range vinfo.VendorLinks(osFamily) {
|
||||
links = append(links, fmt.Sprintf("<%s|%s>",
|
||||
v, k))
|
||||
for _, v := range vinfo.CveContents.PrimarySrcURLs(config.Conf.Lang, osFamily, vinfo.CveID) {
|
||||
links = append(links, fmt.Sprintf("<%s|%s>", v.Value, v.Type))
|
||||
}
|
||||
|
||||
v := fmt.Sprintf("<%s|%s> %s (%s)",
|
||||
@@ -303,9 +302,8 @@ func attachmentText(vinfo models.VulnInfo, osFamily string, cweDict map[string]m
|
||||
}
|
||||
|
||||
mitigation := ""
|
||||
if vinfo.Mitigations(osFamily)[0].Type != models.Unknown {
|
||||
mitigation = fmt.Sprintf("\nMitigation:\n```%s```\n",
|
||||
vinfo.Mitigations(osFamily)[0].Value)
|
||||
for _, m := range vinfo.Mitigations {
|
||||
mitigation = fmt.Sprintf("\nMitigation:\n<%s|%s>", m.URL, m.CveContentType)
|
||||
}
|
||||
|
||||
return fmt.Sprintf("*%4.1f (%s)* %s %s\n%s\n```\n%s\n```%s\n%s\n",
|
||||
|
||||
@@ -1,81 +0,0 @@
|
||||
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
|
||||
}
|
||||
@@ -2,9 +2,9 @@ package report
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"log/syslog"
|
||||
"strings"
|
||||
|
||||
syslog "github.com/RackSec/srslog"
|
||||
"golang.org/x/xerrors"
|
||||
|
||||
"github.com/future-architect/vuls/config"
|
||||
@@ -59,7 +59,7 @@ func (w SyslogWriter) encodeSyslog(result models.ScanResult) (messages []string)
|
||||
kvPairs = append(kvPairs, fmt.Sprintf(`packages="%s"`, pkgs))
|
||||
|
||||
kvPairs = append(kvPairs, fmt.Sprintf(`cve_id="%s"`, cveID))
|
||||
for _, cvss := range vinfo.Cvss2Scores(result.Family) {
|
||||
for _, cvss := range vinfo.Cvss2Scores() {
|
||||
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))
|
||||
}
|
||||
@@ -69,7 +69,7 @@ func (w SyslogWriter) encodeSyslog(result models.ScanResult) (messages []string)
|
||||
kvPairs = append(kvPairs, fmt.Sprintf(`cvss_vector_%s_v3="%s"`, cvss.Type, cvss.Value.Vector))
|
||||
}
|
||||
|
||||
if content, ok := vinfo.CveContents[models.NvdXML]; ok {
|
||||
if content, ok := vinfo.CveContents[models.Nvd]; ok {
|
||||
cwes := strings.Join(content.CweIDs, ",")
|
||||
kvPairs = append(kvPairs, fmt.Sprintf(`cwe_ids="%s"`, cwes))
|
||||
if config.Conf.Syslog.Verbose {
|
||||
|
||||
@@ -33,11 +33,14 @@ func TestSyslogWriterEncodeSyslog(t *testing.T) {
|
||||
models.PackageFixStatus{Name: "pkg4"},
|
||||
},
|
||||
CveContents: models.CveContents{
|
||||
models.NvdXML: models.CveContent{
|
||||
models.Nvd: models.CveContent{
|
||||
Cvss2Score: 5.0,
|
||||
Cvss2Vector: "AV:L/AC:L/Au:N/C:N/I:N/A:C",
|
||||
Cvss2Severity: "MEDIUM",
|
||||
CweIDs: []string{"CWE-20"},
|
||||
Cvss3Score: 9.8,
|
||||
Cvss3Vector: "AV:L/AC:L/Au:N/C:N/I:N/A:C",
|
||||
Cvss3Severity: "HIGH",
|
||||
},
|
||||
},
|
||||
},
|
||||
@@ -45,9 +48,10 @@ func TestSyslogWriterEncodeSyslog(t *testing.T) {
|
||||
},
|
||||
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"`,
|
||||
`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_nvd_v2="5.00" cvss_vector_nvd_v2="AV:L/AC:L/Au:N/C:N/I:N/A:C" cvss_score_nvd_v3="9.80" cvss_vector_nvd_v3="AV:L/AC:L/Au:N/C:N/I:N/A:C" cwe_ids="CWE-20"`,
|
||||
},
|
||||
},
|
||||
// 1
|
||||
{
|
||||
result: models.ScanResult{
|
||||
ScannedAt: time.Date(2018, 6, 13, 17, 10, 0, 0, time.UTC),
|
||||
@@ -62,10 +66,11 @@ func TestSyslogWriterEncodeSyslog(t *testing.T) {
|
||||
},
|
||||
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)",
|
||||
Cvss3Score: 5.0,
|
||||
Cvss3Severity: "Medium",
|
||||
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)",
|
||||
},
|
||||
},
|
||||
},
|
||||
|
||||
@@ -150,8 +150,6 @@ func keybindings(g *gocui.Gui) (err error) {
|
||||
// errs = append(errs, g.SetKeybinding("msg", gocui.KeyEnter, gocui.ModNone, delMsg))
|
||||
// errs = append(errs, g.SetKeybinding("detail", gocui.KeyEnter, gocui.ModNone, showMsg))
|
||||
|
||||
//TODO Help Ctrl-h
|
||||
|
||||
errs = append(errs, g.SetKeybinding("", gocui.KeyCtrlC, gocui.ModNone, quit))
|
||||
// errs = append(errs, g.SetKeybinding("side", gocui.KeyEnter, gocui.ModNone, getLine))
|
||||
// errs = append(errs, g.SetKeybinding("msg", gocui.KeyEnter, gocui.ModNone, delMsg))
|
||||
@@ -619,14 +617,16 @@ func summaryLines(r models.ScanResult) string {
|
||||
|
||||
av := vinfo.AttackVector()
|
||||
for _, pname := range vinfo.AffectedPackages.Names() {
|
||||
if r.Packages[pname].HasPortScanSuccessOn() {
|
||||
if r.Packages[pname].HasReachablePort() {
|
||||
av = fmt.Sprintf("%s ◉", av)
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
exploits := ""
|
||||
if 0 < len(vinfo.Exploits) || 0 < len(vinfo.Metasploits) {
|
||||
if 0 < len(vinfo.Metasploits) {
|
||||
exploits = "EXP"
|
||||
} else if 0 < len(vinfo.Exploits) {
|
||||
exploits = "POC"
|
||||
}
|
||||
|
||||
@@ -719,18 +719,18 @@ func setChangelogLayout(g *gocui.Gui) error {
|
||||
|
||||
if len(pack.AffectedProcs) != 0 {
|
||||
for _, p := range pack.AffectedProcs {
|
||||
if len(p.ListenPorts) == 0 {
|
||||
if len(p.ListenPortStats) == 0 {
|
||||
lines = append(lines, fmt.Sprintf(" * PID: %s %s Port: []",
|
||||
p.PID, p.Name))
|
||||
continue
|
||||
}
|
||||
|
||||
var ports []string
|
||||
for _, pp := range p.ListenPorts {
|
||||
if len(pp.PortScanSuccessOn) == 0 {
|
||||
ports = append(ports, fmt.Sprintf("%s:%s", pp.Address, pp.Port))
|
||||
for _, pp := range p.ListenPortStats {
|
||||
if len(pp.PortReachableTo) == 0 {
|
||||
ports = append(ports, fmt.Sprintf("%s:%s", pp.BindAddress, pp.Port))
|
||||
} else {
|
||||
ports = append(ports, fmt.Sprintf("%s:%s(◉ Scannable: %s)", pp.Address, pp.Port, pp.PortScanSuccessOn))
|
||||
ports = append(ports, fmt.Sprintf("%s:%s(◉ Scannable: %s)", pp.BindAddress, pp.Port, pp.PortReachableTo))
|
||||
}
|
||||
}
|
||||
|
||||
@@ -864,6 +864,7 @@ type dataForTmpl struct {
|
||||
Metasploits []models.Metasploit
|
||||
Summary string
|
||||
Mitigation string
|
||||
PatchURLs []string
|
||||
Confidences models.Confidences
|
||||
Cwes []models.CweDictEntry
|
||||
Alerts []models.Alert
|
||||
@@ -892,14 +893,8 @@ func detailLines() (string, error) {
|
||||
|
||||
vinfo := vinfos[currentVinfo]
|
||||
links := []string{}
|
||||
if strings.HasPrefix(vinfo.CveID, "CVE-") {
|
||||
links = append(links, vinfo.CveContents.SourceLinks(
|
||||
config.Conf.Lang, r.Family, vinfo.CveID)[0].Value,
|
||||
vinfo.Cvss2CalcURL(),
|
||||
vinfo.Cvss3CalcURL())
|
||||
}
|
||||
for _, url := range vinfo.VendorLinks(r.Family) {
|
||||
links = append(links, url)
|
||||
for _, r := range vinfo.CveContents.PrimarySrcURLs(config.Conf.Lang, r.Family, vinfo.CveID) {
|
||||
links = append(links, r.Value)
|
||||
}
|
||||
|
||||
refsMap := map[string]models.Reference{}
|
||||
@@ -922,25 +917,29 @@ func detailLines() (string, error) {
|
||||
}
|
||||
|
||||
summary := vinfo.Summaries(r.Lang, r.Family)[0]
|
||||
mitigation := vinfo.Mitigations(r.Family)[0]
|
||||
|
||||
mitigations := []string{}
|
||||
for _, m := range vinfo.Mitigations {
|
||||
switch m.CveContentType {
|
||||
case models.RedHatAPI, models.Microsoft:
|
||||
mitigations = append(mitigations,
|
||||
fmt.Sprintf("%s (%s)", m.Mitigation, m.CveContentType))
|
||||
case models.Nvd:
|
||||
mitigations = append(mitigations,
|
||||
fmt.Sprintf("* %s (%s)", m.URL, m.CveContentType))
|
||||
default:
|
||||
util.Log.Errorf("Unknown CveContentType: %s", m)
|
||||
}
|
||||
}
|
||||
|
||||
table := uitable.New()
|
||||
table.MaxColWidth = maxColWidth
|
||||
table.Wrap = true
|
||||
scores := append(vinfo.Cvss3Scores(), vinfo.Cvss2Scores(r.Family)...)
|
||||
scores := append(vinfo.Cvss3Scores(), vinfo.Cvss2Scores()...)
|
||||
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...)
|
||||
@@ -960,7 +959,8 @@ func detailLines() (string, error) {
|
||||
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),
|
||||
Mitigation: strings.Join(mitigations, "\n"),
|
||||
PatchURLs: vinfo.CveContents.PatchURLs(),
|
||||
Confidences: vinfo.Confidences,
|
||||
Cwes: cwes,
|
||||
Links: util.Distinct(links),
|
||||
@@ -989,13 +989,18 @@ Summary
|
||||
|
||||
Mitigation
|
||||
-----------
|
||||
{{.Mitigation }}
|
||||
{{.Mitigation }}
|
||||
|
||||
Links
|
||||
Primary Src
|
||||
-----------
|
||||
{{range $link := .Links -}}
|
||||
* {{$link}}
|
||||
{{end}}
|
||||
Patch
|
||||
-----------
|
||||
{{range $url := .PatchURLs -}}
|
||||
* {{$url}}
|
||||
{{end}}
|
||||
CWE
|
||||
-----------
|
||||
{{range .Cwes -}}
|
||||
|
||||
124
report/util.go
124
report/util.go
@@ -22,7 +22,11 @@ import (
|
||||
"golang.org/x/xerrors"
|
||||
)
|
||||
|
||||
const maxColWidth = 100
|
||||
const (
|
||||
vulsOpenTag = "<vulsreport>"
|
||||
vulsCloseTag = "</vulsreport>"
|
||||
maxColWidth = 100
|
||||
)
|
||||
|
||||
func formatScanSummary(rs ...models.ScanResult) string {
|
||||
table := uitable.New()
|
||||
@@ -38,6 +42,12 @@ func formatScanSummary(rs ...models.ScanResult) string {
|
||||
fmt.Sprintf("%s%s", r.Family, r.Release),
|
||||
r.FormatUpdatablePacksSummary(),
|
||||
}
|
||||
if 0 < len(r.WordPressPackages) {
|
||||
cols = append(cols, fmt.Sprintf("%d WordPress pkgs", len(r.WordPressPackages)))
|
||||
}
|
||||
if 0 < len(r.LibraryScanners) {
|
||||
cols = append(cols, fmt.Sprintf("%d libs", r.LibraryScanners.Total()))
|
||||
}
|
||||
} else {
|
||||
cols = []interface{}{
|
||||
r.FormatServerName(),
|
||||
@@ -49,8 +59,7 @@ func formatScanSummary(rs ...models.ScanResult) string {
|
||||
table.AddRow(cols...)
|
||||
|
||||
if len(r.Warnings) != 0 {
|
||||
warnMsgs = append(warnMsgs, fmt.Sprintf("Warning for %s: %s",
|
||||
r.FormatServerName(), r.Warnings))
|
||||
warnMsgs = append(warnMsgs, fmt.Sprintf("Warning: %s", r.Warnings))
|
||||
}
|
||||
}
|
||||
return fmt.Sprintf("%s\n\n%s", table, strings.Join(
|
||||
@@ -136,7 +145,7 @@ No CVE-IDs are found in updatable packages.
|
||||
if strings.HasPrefix(vinfo.CveID, "CVE-") {
|
||||
link = fmt.Sprintf("https://nvd.nist.gov/vuln/detail/%s", vinfo.CveID)
|
||||
} else if strings.HasPrefix(vinfo.CveID, "WPVDBID-") {
|
||||
link = fmt.Sprintf("https://wpvulndb.com/vulnerabilities/%s", strings.TrimPrefix(vinfo.CveID, "WPVDBID-"))
|
||||
link = fmt.Sprintf("https://wpscan.com/vulnerabilities/%s", strings.TrimPrefix(vinfo.CveID, "WPVDBID-"))
|
||||
}
|
||||
|
||||
data = append(data, []string{
|
||||
@@ -204,7 +213,7 @@ No CVE-IDs are found in updatable packages.
|
||||
}
|
||||
}
|
||||
|
||||
for _, cvss := range vuln.Cvss2Scores(r.Family) {
|
||||
for _, cvss := range vuln.Cvss2Scores() {
|
||||
if cvssstr := cvss.Value.Format(); cvssstr != "" {
|
||||
data = append(data, []string{string(cvss.Type), cvssstr})
|
||||
}
|
||||
@@ -213,38 +222,18 @@ No CVE-IDs are found in updatable packages.
|
||||
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})
|
||||
for _, m := range vuln.Mitigations {
|
||||
data = append(data, []string{"Mitigation", m.URL})
|
||||
}
|
||||
|
||||
cweURLs, top10URLs := []string{}, []string{}
|
||||
cweTop25URLs, sansTop25URLs := []string{}, []string{}
|
||||
for _, v := range vuln.CveContents.UniqCweIDs(r.Family) {
|
||||
name, url, top10Rank, top10URL, cweTop25Rank, cweTop25URL, sansTop25Rank, sansTop25URL := r.CweDict.Get(v.Value, r.Lang)
|
||||
if top10Rank != "" {
|
||||
data = append(data, []string{"CWE",
|
||||
fmt.Sprintf("[OWASP Top%s] %s: %s (%s)",
|
||||
top10Rank, v.Value, name, v.Type)})
|
||||
top10URLs = append(top10URLs, top10URL)
|
||||
}
|
||||
if cweTop25Rank != "" {
|
||||
data = append(data, []string{"CWE",
|
||||
fmt.Sprintf("[CWE Top%s] %s: %s (%s)",
|
||||
cweTop25Rank, v.Value, name, v.Type)})
|
||||
cweTop25URLs = append(cweTop25URLs, cweTop25URL)
|
||||
}
|
||||
if sansTop25Rank != "" {
|
||||
data = append(data, []string{"CWE",
|
||||
fmt.Sprintf("[CWE/SANS Top%s] %s: %s (%s)",
|
||||
sansTop25Rank, v.Value, name, v.Type)})
|
||||
sansTop25URLs = append(sansTop25URLs, sansTop25URL)
|
||||
}
|
||||
if top10Rank == "" && cweTop25Rank == "" && sansTop25Rank == "" {
|
||||
data = append(data, []string{"CWE", fmt.Sprintf("%s: %s (%s)",
|
||||
v.Value, name, v.Type)})
|
||||
}
|
||||
cweURLs = append(cweURLs, url)
|
||||
links := vuln.CveContents.PrimarySrcURLs(
|
||||
config.Conf.Lang, r.Family, vuln.CveID)
|
||||
for _, link := range links {
|
||||
data = append(data, []string{"Primary Src", link.Value})
|
||||
}
|
||||
|
||||
for _, url := range vuln.CveContents.PatchURLs() {
|
||||
data = append(data, []string{"Patch", url})
|
||||
}
|
||||
|
||||
vuln.AffectedPackages.Sort()
|
||||
@@ -262,17 +251,17 @@ No CVE-IDs are found in updatable packages.
|
||||
|
||||
if len(pack.AffectedProcs) != 0 {
|
||||
for _, p := range pack.AffectedProcs {
|
||||
if len(p.ListenPorts) == 0 {
|
||||
if len(p.ListenPortStats) == 0 {
|
||||
data = append(data, []string{"",
|
||||
fmt.Sprintf(" - PID: %s %s, Port: []", p.PID, p.Name)})
|
||||
}
|
||||
|
||||
var ports []string
|
||||
for _, pp := range p.ListenPorts {
|
||||
if len(pp.PortScanSuccessOn) == 0 {
|
||||
ports = append(ports, fmt.Sprintf("%s:%s", pp.Address, pp.Port))
|
||||
for _, pp := range p.ListenPortStats {
|
||||
if len(pp.PortReachableTo) == 0 {
|
||||
ports = append(ports, fmt.Sprintf("%s:%s", pp.BindAddress, pp.Port))
|
||||
} else {
|
||||
ports = append(ports, fmt.Sprintf("%s:%s(◉ Scannable: %s)", pp.Address, pp.Port, pp.PortScanSuccessOn))
|
||||
ports = append(ports, fmt.Sprintf("%s:%s(◉ Scannable: %s)", pp.BindAddress, pp.Port, pp.PortReachableTo))
|
||||
}
|
||||
}
|
||||
|
||||
@@ -320,23 +309,35 @@ No CVE-IDs are found in updatable packages.
|
||||
data = append(data, []string{"Confidence", confidence.String()})
|
||||
}
|
||||
|
||||
if strings.HasPrefix(vuln.CveID, "CVE-") {
|
||||
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()})
|
||||
cweURLs, top10URLs := []string{}, []string{}
|
||||
cweTop25URLs, sansTop25URLs := []string{}, []string{}
|
||||
for _, v := range vuln.CveContents.UniqCweIDs(r.Family) {
|
||||
name, url, top10Rank, top10URL, cweTop25Rank, cweTop25URL, sansTop25Rank, sansTop25URL := r.CweDict.Get(v.Value, r.Lang)
|
||||
if top10Rank != "" {
|
||||
data = append(data, []string{"CWE",
|
||||
fmt.Sprintf("[OWASP Top%s] %s: %s (%s)",
|
||||
top10Rank, v.Value, name, v.Type)})
|
||||
top10URLs = append(top10URLs, top10URL)
|
||||
}
|
||||
if 0 < len(vuln.Cvss3Scores()) {
|
||||
data = append(data, []string{"CVSSv3 Calc", vuln.Cvss3CalcURL()})
|
||||
if cweTop25Rank != "" {
|
||||
data = append(data, []string{"CWE",
|
||||
fmt.Sprintf("[CWE Top%s] %s: %s (%s)",
|
||||
cweTop25Rank, v.Value, name, v.Type)})
|
||||
cweTop25URLs = append(cweTop25URLs, cweTop25URL)
|
||||
}
|
||||
if sansTop25Rank != "" {
|
||||
data = append(data, []string{"CWE",
|
||||
fmt.Sprintf("[CWE/SANS Top%s] %s: %s (%s)",
|
||||
sansTop25Rank, v.Value, name, v.Type)})
|
||||
sansTop25URLs = append(sansTop25URLs, sansTop25URL)
|
||||
}
|
||||
if top10Rank == "" && cweTop25Rank == "" && sansTop25Rank == "" {
|
||||
data = append(data, []string{"CWE", fmt.Sprintf("%s: %s (%s)",
|
||||
v.Value, name, v.Type)})
|
||||
}
|
||||
cweURLs = append(cweURLs, url)
|
||||
}
|
||||
|
||||
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})
|
||||
}
|
||||
@@ -397,7 +398,7 @@ func formatCsvList(r models.ScanResult, path string) error {
|
||||
if strings.HasPrefix(vinfo.CveID, "CVE-") {
|
||||
link = fmt.Sprintf("https://nvd.nist.gov/vuln/detail/%s", vinfo.CveID)
|
||||
} else if strings.HasPrefix(vinfo.CveID, "WPVDBID-") {
|
||||
link = fmt.Sprintf("https://wpvulndb.com/vulnerabilities/%s", strings.TrimPrefix(vinfo.CveID, "WPVDBID-"))
|
||||
link = fmt.Sprintf("https://wpscan.com/vulnerabilities/%s", strings.TrimPrefix(vinfo.CveID, "WPVDBID-"))
|
||||
}
|
||||
|
||||
data = append(data, []string{
|
||||
@@ -442,16 +443,25 @@ func formatChangelogs(r models.ScanResult) string {
|
||||
}
|
||||
return strings.Join(buf, "\n")
|
||||
}
|
||||
func useScannedCves(r *models.ScanResult) bool {
|
||||
|
||||
func reuseScannedCves(r *models.ScanResult) bool {
|
||||
switch r.Family {
|
||||
case
|
||||
config.FreeBSD,
|
||||
config.Raspbian:
|
||||
return true
|
||||
}
|
||||
if isTrivyResult(r) {
|
||||
return true
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
func isTrivyResult(r *models.ScanResult) bool {
|
||||
_, ok := r.Optional["trivy-target"]
|
||||
return ok
|
||||
}
|
||||
|
||||
func needToRefreshCve(r models.ScanResult) bool {
|
||||
if r.Lang != config.Conf.Lang {
|
||||
return true
|
||||
@@ -555,7 +565,7 @@ func getDiffCves(previous, current models.ScanResult) models.VulnInfos {
|
||||
|
||||
// TODO commented out because 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 uncomented after integration with ghost https://github.com/knqyf263/gost
|
||||
// This logic will be uncomented after integration with gost https://github.com/knqyf263/gost
|
||||
// } else if isCveFixed(v, previous) {
|
||||
// updated[v.CveID] = v
|
||||
// util.Log.Debugf("fixed: %s", v.CveID)
|
||||
@@ -596,7 +606,7 @@ func isCveFixed(current models.VulnInfo, previous models.ScanResult) bool {
|
||||
|
||||
func isCveInfoUpdated(cveID string, previous, current models.ScanResult) bool {
|
||||
cTypes := []models.CveContentType{
|
||||
models.NvdXML,
|
||||
models.Nvd,
|
||||
models.Jvn,
|
||||
models.NewCveContentType(current.Family),
|
||||
}
|
||||
|
||||
@@ -42,7 +42,7 @@ func TestIsCveInfoUpdated(t *testing.T) {
|
||||
CveID: "CVE-2017-0001",
|
||||
CveContents: models.NewCveContents(
|
||||
models.CveContent{
|
||||
Type: models.NvdXML,
|
||||
Type: models.Nvd,
|
||||
CveID: "CVE-2017-0001",
|
||||
LastModified: time.Time{},
|
||||
},
|
||||
@@ -56,7 +56,7 @@ func TestIsCveInfoUpdated(t *testing.T) {
|
||||
CveID: "CVE-2017-0001",
|
||||
CveContents: models.NewCveContents(
|
||||
models.CveContent{
|
||||
Type: models.NvdXML,
|
||||
Type: models.Nvd,
|
||||
CveID: "CVE-2017-0001",
|
||||
LastModified: time.Time{},
|
||||
},
|
||||
@@ -113,7 +113,7 @@ func TestIsCveInfoUpdated(t *testing.T) {
|
||||
CveID: "CVE-2017-0003",
|
||||
CveContents: models.NewCveContents(
|
||||
models.CveContent{
|
||||
Type: models.NvdXML,
|
||||
Type: models.Nvd,
|
||||
CveID: "CVE-2017-0002",
|
||||
LastModified: new,
|
||||
},
|
||||
@@ -128,7 +128,7 @@ func TestIsCveInfoUpdated(t *testing.T) {
|
||||
CveID: "CVE-2017-0003",
|
||||
CveContents: models.NewCveContents(
|
||||
models.CveContent{
|
||||
Type: models.NvdXML,
|
||||
Type: models.Nvd,
|
||||
CveID: "CVE-2017-0002",
|
||||
LastModified: old,
|
||||
},
|
||||
@@ -150,7 +150,7 @@ func TestIsCveInfoUpdated(t *testing.T) {
|
||||
CveID: "CVE-2017-0004",
|
||||
CveContents: models.NewCveContents(
|
||||
models.CveContent{
|
||||
Type: models.NvdXML,
|
||||
Type: models.Nvd,
|
||||
CveID: "CVE-2017-0002",
|
||||
LastModified: old,
|
||||
},
|
||||
@@ -267,7 +267,7 @@ func TestDiff(t *testing.T) {
|
||||
NewVersion: "5.1.73",
|
||||
NewRelease: "8.el6_8",
|
||||
Repository: "",
|
||||
Changelog: models.Changelog{
|
||||
Changelog: &models.Changelog{
|
||||
Contents: "",
|
||||
Method: "",
|
||||
},
|
||||
@@ -305,7 +305,7 @@ func TestDiff(t *testing.T) {
|
||||
NewVersion: "5.1.73",
|
||||
NewRelease: "8.el6_8",
|
||||
Repository: "",
|
||||
Changelog: models.Changelog{
|
||||
Changelog: &models.Changelog{
|
||||
Contents: "",
|
||||
Method: "",
|
||||
},
|
||||
@@ -356,7 +356,7 @@ func TestIsCveFixed(t *testing.T) {
|
||||
},
|
||||
CveContents: models.NewCveContents(
|
||||
models.CveContent{
|
||||
Type: models.NvdXML,
|
||||
Type: models.Nvd,
|
||||
CveID: "CVE-2016-6662",
|
||||
LastModified: time.Time{},
|
||||
},
|
||||
@@ -374,7 +374,7 @@ func TestIsCveFixed(t *testing.T) {
|
||||
},
|
||||
CveContents: models.NewCveContents(
|
||||
models.CveContent{
|
||||
Type: models.NvdXML,
|
||||
Type: models.Nvd,
|
||||
CveID: "CVE-2016-6662",
|
||||
LastModified: time.Time{},
|
||||
},
|
||||
@@ -397,7 +397,7 @@ func TestIsCveFixed(t *testing.T) {
|
||||
},
|
||||
CveContents: models.NewCveContents(
|
||||
models.CveContent{
|
||||
Type: models.NvdXML,
|
||||
Type: models.Nvd,
|
||||
CveID: "CVE-2016-6662",
|
||||
LastModified: time.Time{},
|
||||
},
|
||||
@@ -415,7 +415,7 @@ func TestIsCveFixed(t *testing.T) {
|
||||
},
|
||||
CveContents: models.NewCveContents(
|
||||
models.CveContent{
|
||||
Type: models.NvdXML,
|
||||
Type: models.Nvd,
|
||||
CveID: "CVE-2016-6662",
|
||||
LastModified: time.Time{},
|
||||
},
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
package report
|
||||
package saas
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
@@ -10,7 +10,6 @@ import (
|
||||
"os"
|
||||
"path"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"github.com/aws/aws-sdk-go/aws"
|
||||
"github.com/aws/aws-sdk-go/aws/credentials"
|
||||
@@ -23,8 +22,8 @@ import (
|
||||
"golang.org/x/xerrors"
|
||||
)
|
||||
|
||||
// SaasWriter writes results to SaaS
|
||||
type SaasWriter struct{}
|
||||
// Writer writes results to SaaS
|
||||
type Writer struct{}
|
||||
|
||||
// TempCredential : TempCredential
|
||||
type TempCredential struct {
|
||||
@@ -42,7 +41,7 @@ type payload struct {
|
||||
}
|
||||
|
||||
// UploadSaas : UploadSaas
|
||||
func (w SaasWriter) Write(rs ...models.ScanResult) (err error) {
|
||||
func (w Writer) Write(rs ...models.ScanResult) (err error) {
|
||||
// dir string, configPath string, config *c.Config
|
||||
if len(rs) == 0 {
|
||||
return nil
|
||||
@@ -122,7 +121,7 @@ func (w SaasWriter) Write(rs ...models.ScanResult) (err error) {
|
||||
|
||||
svc := s3.New(sess)
|
||||
for _, r := range rs {
|
||||
s3Key := renameKeyNameUTC(r.ScannedAt, r.ServerUUID, r.Container)
|
||||
s3Key := renameKeyName(r.ServerUUID, r.Container)
|
||||
var b []byte
|
||||
if b, err = json.Marshal(r); err != nil {
|
||||
return xerrors.Errorf("Failed to Marshal to JSON: %w", err)
|
||||
@@ -143,10 +142,9 @@ func (w SaasWriter) Write(rs ...models.ScanResult) (err error) {
|
||||
return nil
|
||||
}
|
||||
|
||||
func renameKeyNameUTC(scannedAt time.Time, uuid string, container models.Container) string {
|
||||
timestr := scannedAt.UTC().Format(time.RFC3339)
|
||||
func renameKeyName(uuid string, container models.Container) string {
|
||||
if len(container.ContainerID) == 0 {
|
||||
return fmt.Sprintf("%s/%s.json", timestr, uuid)
|
||||
return fmt.Sprintf("%s.json", uuid)
|
||||
}
|
||||
return fmt.Sprintf("%s/%s@%s.json", timestr, container.UUID, uuid)
|
||||
return fmt.Sprintf("%s@%s.json", container.UUID, uuid)
|
||||
}
|
||||
208
saas/uuid.go
Normal file
208
saas/uuid.go
Normal file
@@ -0,0 +1,208 @@
|
||||
package saas
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"fmt"
|
||||
"io/ioutil"
|
||||
"os"
|
||||
"reflect"
|
||||
"regexp"
|
||||
"sort"
|
||||
"strings"
|
||||
|
||||
"github.com/BurntSushi/toml"
|
||||
c "github.com/future-architect/vuls/config"
|
||||
"github.com/future-architect/vuls/models"
|
||||
"github.com/future-architect/vuls/util"
|
||||
"github.com/hashicorp/go-uuid"
|
||||
"golang.org/x/xerrors"
|
||||
)
|
||||
|
||||
const reUUID = "[\\da-f]{8}-[\\da-f]{4}-[\\da-f]{4}-[\\da-f]{4}-[\\da-f]{12}"
|
||||
|
||||
// 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.
|
||||
func getOrCreateServerUUID(r models.ScanResult, server c.ServerInfo) (serverUUID string, err error) {
|
||||
if id, ok := server.UUIDs[r.ServerName]; !ok {
|
||||
if serverUUID, err = uuid.GenerateUUID(); err != nil {
|
||||
return "", xerrors.Errorf("Failed to generate UUID: %w", err)
|
||||
}
|
||||
} else {
|
||||
matched, err := regexp.MatchString(reUUID, id)
|
||||
if !matched || err != nil {
|
||||
if serverUUID, err = uuid.GenerateUUID(); err != nil {
|
||||
return "", xerrors.Errorf("Failed to generate UUID: %w", err)
|
||||
}
|
||||
}
|
||||
}
|
||||
return serverUUID, nil
|
||||
}
|
||||
|
||||
// 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) (err 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
|
||||
})
|
||||
|
||||
re := regexp.MustCompile(reUUID)
|
||||
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)
|
||||
serverUUID, err := getOrCreateServerUUID(r, server)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if serverUUID != "" {
|
||||
server.UUIDs[r.ServerName] = serverUUID
|
||||
}
|
||||
} else {
|
||||
name = r.ServerName
|
||||
}
|
||||
|
||||
if id, ok := server.UUIDs[name]; ok {
|
||||
ok := re.MatchString(id)
|
||||
if !ok || 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
|
||||
serverUUID, err := uuid.GenerateUUID()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
server.UUIDs[name] = serverUUID
|
||||
c.Conf.Servers[r.ServerName] = server
|
||||
|
||||
if r.IsContainer() {
|
||||
results[i].Container.UUID = serverUUID
|
||||
results[i].ServerUUID = server.UUIDs[r.ServerName]
|
||||
} else {
|
||||
results[i].ServerUUID = serverUUID
|
||||
}
|
||||
}
|
||||
|
||||
for name, server := range c.Conf.Servers {
|
||||
server = cleanForTOMLEncoding(server, c.Conf.Default)
|
||||
c.Conf.Servers[name] = server
|
||||
}
|
||||
if c.Conf.Default.WordPress != nil && c.Conf.Default.WordPress.IsZero() {
|
||||
c.Conf.Default.WordPress = nil
|
||||
}
|
||||
|
||||
c := struct {
|
||||
Saas *c.SaasConf `toml:"saas"`
|
||||
Default c.ServerInfo `toml:"default"`
|
||||
Servers map[string]c.ServerInfo `toml:"servers"`
|
||||
}{
|
||||
Saas: &c.Conf.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 xerrors.Errorf("Failed to lstat %s: %w", configPath, err)
|
||||
}
|
||||
realPath := configPath
|
||||
if info.Mode()&os.ModeSymlink == os.ModeSymlink {
|
||||
if realPath, err = os.Readlink(configPath); err != nil {
|
||||
return xerrors.Errorf("Failed to Read link %s: %w", configPath, err)
|
||||
}
|
||||
}
|
||||
if err := os.Rename(realPath, realPath+".bak"); err != nil {
|
||||
return xerrors.Errorf("Failed to rename %s: %w", configPath, err)
|
||||
}
|
||||
|
||||
var buf bytes.Buffer
|
||||
if err := toml.NewEncoder(&buf).Encode(c); err != nil {
|
||||
return xerrors.Errorf("Failed to encode to toml: %w", err)
|
||||
}
|
||||
str := strings.Replace(buf.String(), "\n [", "\n\n [", -1)
|
||||
str = fmt.Sprintf("%s\n\n%s",
|
||||
"# See README 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)
|
||||
}
|
||||
}
|
||||
|
||||
if server.WordPress != nil {
|
||||
if server.WordPress.IsZero() || reflect.DeepEqual(server.WordPress, def.WordPress) {
|
||||
server.WordPress = nil
|
||||
}
|
||||
}
|
||||
|
||||
return server
|
||||
}
|
||||
@@ -1,10 +1,9 @@
|
||||
package report
|
||||
package saas
|
||||
|
||||
import (
|
||||
"testing"
|
||||
|
||||
"github.com/future-architect/vuls/config"
|
||||
|
||||
"github.com/future-architect/vuls/models"
|
||||
)
|
||||
|
||||
@@ -73,7 +73,6 @@ func (o *alpine) apkUpdate() error {
|
||||
}
|
||||
|
||||
func (o *alpine) preCure() error {
|
||||
o.log.Infof("Scanning in %s", o.getServerInfo().Mode)
|
||||
if err := o.detectIPAddr(); err != nil {
|
||||
o.log.Warnf("Failed to detect IP addresses: %s", err)
|
||||
o.warns = append(o.warns, err)
|
||||
@@ -92,6 +91,7 @@ func (o *alpine) detectIPAddr() (err error) {
|
||||
}
|
||||
|
||||
func (o *alpine) scanPackages() error {
|
||||
o.log.Infof("Scanning OS pkg in %s", o.getServerInfo().Mode)
|
||||
if err := o.apkUpdate(); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
91
scan/base.go
91
scan/base.go
@@ -35,7 +35,7 @@ type base struct {
|
||||
Platform models.Platform
|
||||
osPackages
|
||||
LibraryScanners []models.LibraryScanner
|
||||
WordPress *models.WordPressPackages
|
||||
WordPress models.WordPressPackages
|
||||
|
||||
log *logrus.Entry
|
||||
errs []error
|
||||
@@ -449,6 +449,7 @@ func (l *base) convertToModel() models.ScanResult {
|
||||
RunningKernel: l.Kernel,
|
||||
Packages: l.Packages,
|
||||
SrcPackages: l.SrcPackages,
|
||||
EnabledDnfModules: l.EnabledDnfModules,
|
||||
WordPressPackages: l.WordPress,
|
||||
LibraryScanners: l.LibraryScanners,
|
||||
Optional: l.ServerInfo.Optional,
|
||||
@@ -525,7 +526,6 @@ func (l *base) parseSystemctlStatus(stdout string) string {
|
||||
}
|
||||
|
||||
func (l *base) scanLibraries() (err error) {
|
||||
// image already detected libraries
|
||||
if len(l.LibraryScanners) != 0 {
|
||||
return nil
|
||||
}
|
||||
@@ -535,6 +535,8 @@ func (l *base) scanLibraries() (err error) {
|
||||
return nil
|
||||
}
|
||||
|
||||
l.log.Info("Scanning Lockfile...")
|
||||
|
||||
libFilemap := map[string][]byte{}
|
||||
detectFiles := l.ServerInfo.Lockfiles
|
||||
|
||||
@@ -598,44 +600,36 @@ func (l *base) scanLibraries() (err error) {
|
||||
return nil
|
||||
}
|
||||
|
||||
// DummyFileInfo is a dummy struct for libscan
|
||||
type DummyFileInfo struct{}
|
||||
|
||||
func (d *DummyFileInfo) Name() string { return "dummy" }
|
||||
func (d *DummyFileInfo) Size() int64 { return 0 }
|
||||
func (d *DummyFileInfo) Mode() os.FileMode { return 0 }
|
||||
// Name is
|
||||
func (d *DummyFileInfo) Name() string { return "dummy" }
|
||||
|
||||
// Size is
|
||||
func (d *DummyFileInfo) Size() int64 { return 0 }
|
||||
|
||||
// Mode is
|
||||
func (d *DummyFileInfo) Mode() os.FileMode { return 0 }
|
||||
|
||||
//ModTime is
|
||||
func (d *DummyFileInfo) ModTime() time.Time { return time.Now() }
|
||||
func (d *DummyFileInfo) IsDir() bool { return false }
|
||||
func (d *DummyFileInfo) Sys() interface{} { return nil }
|
||||
|
||||
// IsDir is
|
||||
func (d *DummyFileInfo) IsDir() bool { return false }
|
||||
|
||||
//Sys is
|
||||
func (d *DummyFileInfo) Sys() interface{} { return nil }
|
||||
|
||||
func (l *base) scanWordPress() (err error) {
|
||||
wpOpts := []string{l.ServerInfo.WordPress.OSUser,
|
||||
l.ServerInfo.WordPress.DocRoot,
|
||||
l.ServerInfo.WordPress.CmdPath,
|
||||
l.ServerInfo.WordPress.WPVulnDBToken,
|
||||
}
|
||||
var isScanWp, hasEmptyOpt bool
|
||||
for _, opt := range wpOpts {
|
||||
if opt != "" {
|
||||
isScanWp = true
|
||||
break
|
||||
} else {
|
||||
hasEmptyOpt = true
|
||||
}
|
||||
}
|
||||
if !isScanWp {
|
||||
if l.ServerInfo.WordPress.IsZero() || l.ServerInfo.Type == config.ServerTypePseudo {
|
||||
return nil
|
||||
}
|
||||
|
||||
if hasEmptyOpt {
|
||||
return xerrors.Errorf("%s has empty WordPress opts: %s",
|
||||
l.getServerInfo().GetServerName(), wpOpts)
|
||||
}
|
||||
|
||||
l.log.Info("Scanning WordPress...")
|
||||
cmd := fmt.Sprintf("sudo -u %s -i -- %s cli version --allow-root",
|
||||
l.ServerInfo.WordPress.OSUser,
|
||||
l.ServerInfo.WordPress.CmdPath)
|
||||
if r := exec(l.ServerInfo, cmd, noSudo); !r.isSuccess() {
|
||||
l.ServerInfo.WordPress.WPVulnDBToken = "secret"
|
||||
return xerrors.Errorf("Failed to exec `%s`. Check the OS user, command path of wp-cli, DocRoot and permission: %#v", cmd, l.ServerInfo.WordPress)
|
||||
}
|
||||
|
||||
@@ -643,7 +637,7 @@ func (l *base) scanWordPress() (err error) {
|
||||
if err != nil {
|
||||
return xerrors.Errorf("Failed to scan wordpress: %w", err)
|
||||
}
|
||||
l.WordPress = wp
|
||||
l.WordPress = *wp
|
||||
return nil
|
||||
}
|
||||
|
||||
@@ -730,6 +724,7 @@ func (l *base) detectWpPlugins() ([]models.WpPackage, error) {
|
||||
}
|
||||
|
||||
func (l *base) scanPorts() (err error) {
|
||||
l.log.Info("Scanning listen port...")
|
||||
dest := l.detectScanDest()
|
||||
open, err := l.execPortsScan(dest)
|
||||
if err != nil {
|
||||
@@ -748,11 +743,11 @@ func (l *base) detectScanDest() map[string][]string {
|
||||
continue
|
||||
}
|
||||
for _, proc := range p.AffectedProcs {
|
||||
if proc.ListenPorts == nil {
|
||||
if proc.ListenPortStats == nil {
|
||||
continue
|
||||
}
|
||||
for _, port := range proc.ListenPorts {
|
||||
scanIPPortsMap[port.Address] = append(scanIPPortsMap[port.Address], port.Port)
|
||||
for _, port := range proc.ListenPortStats {
|
||||
scanIPPortsMap[port.BindAddress] = append(scanIPPortsMap[port.BindAddress], port.Port)
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -809,27 +804,31 @@ func (l *base) updatePortStatus(listenIPPorts []string) {
|
||||
continue
|
||||
}
|
||||
for i, proc := range p.AffectedProcs {
|
||||
if proc.ListenPorts == nil {
|
||||
if proc.ListenPortStats == nil {
|
||||
continue
|
||||
}
|
||||
for j, port := range proc.ListenPorts {
|
||||
l.osPackages.Packages[name].AffectedProcs[i].ListenPorts[j].PortScanSuccessOn = l.findPortScanSuccessOn(listenIPPorts, port)
|
||||
for j, port := range proc.ListenPortStats {
|
||||
l.osPackages.Packages[name].AffectedProcs[i].ListenPortStats[j].PortReachableTo = l.findPortTestSuccessOn(listenIPPorts, port)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func (l *base) findPortScanSuccessOn(listenIPPorts []string, searchListenPort models.ListenPort) []string {
|
||||
func (l *base) findPortTestSuccessOn(listenIPPorts []string, searchListenPort models.PortStat) []string {
|
||||
addrs := []string{}
|
||||
|
||||
for _, ipPort := range listenIPPorts {
|
||||
ipPort := l.parseListenPorts(ipPort)
|
||||
if searchListenPort.Address == "*" {
|
||||
ipPort, err := models.NewPortStat(ipPort)
|
||||
if err != nil {
|
||||
l.log.Warnf("Failed to find: %+v", err)
|
||||
continue
|
||||
}
|
||||
if searchListenPort.BindAddress == "*" {
|
||||
if searchListenPort.Port == ipPort.Port {
|
||||
addrs = append(addrs, ipPort.Address)
|
||||
addrs = append(addrs, ipPort.BindAddress)
|
||||
}
|
||||
} else if searchListenPort.Address == ipPort.Address && searchListenPort.Port == ipPort.Port {
|
||||
addrs = append(addrs, ipPort.Address)
|
||||
} else if searchListenPort.BindAddress == ipPort.BindAddress && searchListenPort.Port == ipPort.Port {
|
||||
addrs = append(addrs, ipPort.BindAddress)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -916,11 +915,3 @@ func (l *base) parseLsOf(stdout string) map[string][]string {
|
||||
}
|
||||
return portPids
|
||||
}
|
||||
|
||||
func (l *base) parseListenPorts(port string) models.ListenPort {
|
||||
sep := strings.LastIndex(port, ":")
|
||||
if sep == -1 {
|
||||
return models.ListenPort{}
|
||||
}
|
||||
return models.ListenPort{Address: port[:sep], Port: port[sep+1:]}
|
||||
}
|
||||
|
||||
@@ -323,7 +323,7 @@ func Test_detectScanDest(t *testing.T) {
|
||||
Version: "1:2.8.4-3",
|
||||
NewVersion: "1:2.8.4-3",
|
||||
AffectedProcs: []models.AffectedProcess{
|
||||
{PID: "21", Name: "sshd", ListenPorts: []models.ListenPort{{Address: "127.0.0.1", Port: "22"}}}, {PID: "10876", Name: "sshd"}},
|
||||
{PID: "21", Name: "sshd", ListenPortStats: []models.PortStat{{BindAddress: "127.0.0.1", Port: "22"}}}, {PID: "10876", Name: "sshd"}},
|
||||
},
|
||||
}},
|
||||
},
|
||||
@@ -337,7 +337,7 @@ func Test_detectScanDest(t *testing.T) {
|
||||
Version: "1:2.8.4-3",
|
||||
NewVersion: "1:2.8.4-3",
|
||||
AffectedProcs: []models.AffectedProcess{
|
||||
{PID: "21", Name: "sshd", ListenPorts: []models.ListenPort{{Address: "127.0.0.1", Port: "22"}}}, {PID: "21", Name: "sshd", ListenPorts: []models.ListenPort{{Address: "127.0.0.1", Port: "22"}}}},
|
||||
{PID: "21", Name: "sshd", ListenPortStats: []models.PortStat{{BindAddress: "127.0.0.1", Port: "22"}}}, {PID: "21", Name: "sshd", ListenPortStats: []models.PortStat{{BindAddress: "127.0.0.1", Port: "22"}}}},
|
||||
},
|
||||
}},
|
||||
},
|
||||
@@ -351,7 +351,7 @@ func Test_detectScanDest(t *testing.T) {
|
||||
Version: "1:2.8.4-3",
|
||||
NewVersion: "1:2.8.4-3",
|
||||
AffectedProcs: []models.AffectedProcess{
|
||||
{PID: "21", Name: "sshd", ListenPorts: []models.ListenPort{{Address: "127.0.0.1", Port: "22"}}}, {PID: "21", Name: "sshd", ListenPorts: []models.ListenPort{{Address: "192.168.1.1", Port: "22"}}}, {PID: "6261", Name: "nginx", ListenPorts: []models.ListenPort{{Address: "127.0.0.1", Port: "80"}}}},
|
||||
{PID: "21", Name: "sshd", ListenPortStats: []models.PortStat{{BindAddress: "127.0.0.1", Port: "22"}}}, {PID: "21", Name: "sshd", ListenPortStats: []models.PortStat{{BindAddress: "192.168.1.1", Port: "22"}}}, {PID: "6261", Name: "nginx", ListenPortStats: []models.PortStat{{BindAddress: "127.0.0.1", Port: "80"}}}},
|
||||
},
|
||||
}},
|
||||
},
|
||||
@@ -366,7 +366,7 @@ func Test_detectScanDest(t *testing.T) {
|
||||
Version: "1:2.8.4-3",
|
||||
NewVersion: "1:2.8.4-3",
|
||||
AffectedProcs: []models.AffectedProcess{
|
||||
{PID: "21", Name: "sshd", ListenPorts: []models.ListenPort{{Address: "*", Port: "22"}}}},
|
||||
{PID: "21", Name: "sshd", ListenPortStats: []models.PortStat{{BindAddress: "*", Port: "22"}}}},
|
||||
},
|
||||
}},
|
||||
ServerInfo: config.ServerInfo{
|
||||
@@ -411,45 +411,45 @@ func Test_updatePortStatus(t *testing.T) {
|
||||
{name: "update_match_single_address",
|
||||
args: args{
|
||||
l: base{osPackages: osPackages{
|
||||
Packages: models.Packages{"libc6": models.Package{Name: "libc6", AffectedProcs: []models.AffectedProcess{{PID: "1", Name: "bash"}, {PID: "75", Name: "sshd", ListenPorts: []models.ListenPort{{Address: "127.0.0.1", Port: "22"}}}}}},
|
||||
Packages: models.Packages{"libc6": models.Package{Name: "libc6", AffectedProcs: []models.AffectedProcess{{PID: "1", Name: "bash"}, {PID: "75", Name: "sshd", ListenPortStats: []models.PortStat{{BindAddress: "127.0.0.1", Port: "22"}}}}}},
|
||||
}},
|
||||
listenIPPorts: []string{"127.0.0.1:22"}},
|
||||
expect: models.Packages{"libc6": models.Package{Name: "libc6", AffectedProcs: []models.AffectedProcess{{PID: "1", Name: "bash"}, {PID: "75", Name: "sshd", ListenPorts: []models.ListenPort{{Address: "127.0.0.1", Port: "22", PortScanSuccessOn: []string{"127.0.0.1"}}}}}}}},
|
||||
expect: models.Packages{"libc6": models.Package{Name: "libc6", AffectedProcs: []models.AffectedProcess{{PID: "1", Name: "bash"}, {PID: "75", Name: "sshd", ListenPortStats: []models.PortStat{{BindAddress: "127.0.0.1", Port: "22", PortReachableTo: []string{"127.0.0.1"}}}}}}}},
|
||||
{name: "update_match_multi_address",
|
||||
args: args{
|
||||
l: base{osPackages: osPackages{
|
||||
Packages: models.Packages{"libc6": models.Package{Name: "libc6", AffectedProcs: []models.AffectedProcess{{PID: "1", Name: "bash"}, {PID: "75", Name: "sshd", ListenPorts: []models.ListenPort{{Address: "127.0.0.1", Port: "22"}, {Address: "192.168.1.1", Port: "22"}}}}}},
|
||||
Packages: models.Packages{"libc6": models.Package{Name: "libc6", AffectedProcs: []models.AffectedProcess{{PID: "1", Name: "bash"}, {PID: "75", Name: "sshd", ListenPortStats: []models.PortStat{{BindAddress: "127.0.0.1", Port: "22"}, {BindAddress: "192.168.1.1", Port: "22"}}}}}},
|
||||
}},
|
||||
listenIPPorts: []string{"127.0.0.1:22", "192.168.1.1:22"}},
|
||||
expect: models.Packages{"libc6": models.Package{Name: "libc6", AffectedProcs: []models.AffectedProcess{{PID: "1", Name: "bash"}, {PID: "75", Name: "sshd", ListenPorts: []models.ListenPort{
|
||||
{Address: "127.0.0.1", Port: "22", PortScanSuccessOn: []string{"127.0.0.1"}},
|
||||
{Address: "192.168.1.1", Port: "22", PortScanSuccessOn: []string{"192.168.1.1"}},
|
||||
expect: models.Packages{"libc6": models.Package{Name: "libc6", AffectedProcs: []models.AffectedProcess{{PID: "1", Name: "bash"}, {PID: "75", Name: "sshd", ListenPortStats: []models.PortStat{
|
||||
{BindAddress: "127.0.0.1", Port: "22", PortReachableTo: []string{"127.0.0.1"}},
|
||||
{BindAddress: "192.168.1.1", Port: "22", PortReachableTo: []string{"192.168.1.1"}},
|
||||
}}}}}},
|
||||
{name: "update_match_asterisk",
|
||||
args: args{
|
||||
l: base{osPackages: osPackages{
|
||||
Packages: models.Packages{"libc6": models.Package{Name: "libc6", AffectedProcs: []models.AffectedProcess{{PID: "1", Name: "bash"}, {PID: "75", Name: "sshd", ListenPorts: []models.ListenPort{{Address: "*", Port: "22"}}}}}},
|
||||
Packages: models.Packages{"libc6": models.Package{Name: "libc6", AffectedProcs: []models.AffectedProcess{{PID: "1", Name: "bash"}, {PID: "75", Name: "sshd", ListenPortStats: []models.PortStat{{BindAddress: "*", Port: "22"}}}}}},
|
||||
}},
|
||||
listenIPPorts: []string{"127.0.0.1:22", "127.0.0.1:80", "192.168.1.1:22"}},
|
||||
expect: models.Packages{"libc6": models.Package{Name: "libc6", AffectedProcs: []models.AffectedProcess{{PID: "1", Name: "bash"}, {PID: "75", Name: "sshd", ListenPorts: []models.ListenPort{
|
||||
{Address: "*", Port: "22", PortScanSuccessOn: []string{"127.0.0.1", "192.168.1.1"}},
|
||||
expect: models.Packages{"libc6": models.Package{Name: "libc6", AffectedProcs: []models.AffectedProcess{{PID: "1", Name: "bash"}, {PID: "75", Name: "sshd", ListenPortStats: []models.PortStat{
|
||||
{BindAddress: "*", Port: "22", PortReachableTo: []string{"127.0.0.1", "192.168.1.1"}},
|
||||
}}}}}},
|
||||
{name: "update_multi_packages",
|
||||
args: args{
|
||||
l: base{osPackages: osPackages{
|
||||
Packages: models.Packages{
|
||||
"packa": models.Package{Name: "packa", AffectedProcs: []models.AffectedProcess{{PID: "75", Name: "sshd", ListenPorts: []models.ListenPort{{Address: "127.0.0.1", Port: "80"}}}}},
|
||||
"packb": models.Package{Name: "packb", AffectedProcs: []models.AffectedProcess{{PID: "75", Name: "sshd", ListenPorts: []models.ListenPort{{Address: "127.0.0.1", Port: "22"}}}}},
|
||||
"packc": models.Package{Name: "packc", AffectedProcs: []models.AffectedProcess{{PID: "75", Name: "sshd", ListenPorts: []models.ListenPort{{Address: "127.0.0.1", Port: "22"}, {Address: "192.168.1.1", Port: "22"}}}}},
|
||||
"packd": models.Package{Name: "packd", AffectedProcs: []models.AffectedProcess{{PID: "75", Name: "sshd", ListenPorts: []models.ListenPort{{Address: "*", Port: "22"}}}}},
|
||||
"packa": models.Package{Name: "packa", AffectedProcs: []models.AffectedProcess{{PID: "75", Name: "sshd", ListenPortStats: []models.PortStat{{BindAddress: "127.0.0.1", Port: "80"}}}}},
|
||||
"packb": models.Package{Name: "packb", AffectedProcs: []models.AffectedProcess{{PID: "75", Name: "sshd", ListenPortStats: []models.PortStat{{BindAddress: "127.0.0.1", Port: "22"}}}}},
|
||||
"packc": models.Package{Name: "packc", AffectedProcs: []models.AffectedProcess{{PID: "75", Name: "sshd", ListenPortStats: []models.PortStat{{BindAddress: "127.0.0.1", Port: "22"}, {BindAddress: "192.168.1.1", Port: "22"}}}}},
|
||||
"packd": models.Package{Name: "packd", AffectedProcs: []models.AffectedProcess{{PID: "75", Name: "sshd", ListenPortStats: []models.PortStat{{BindAddress: "*", Port: "22"}}}}},
|
||||
},
|
||||
}},
|
||||
listenIPPorts: []string{"127.0.0.1:22", "192.168.1.1:22"}},
|
||||
expect: models.Packages{
|
||||
"packa": models.Package{Name: "packa", AffectedProcs: []models.AffectedProcess{{PID: "75", Name: "sshd", ListenPorts: []models.ListenPort{{Address: "127.0.0.1", Port: "80", PortScanSuccessOn: []string{}}}}}},
|
||||
"packb": models.Package{Name: "packb", AffectedProcs: []models.AffectedProcess{{PID: "75", Name: "sshd", ListenPorts: []models.ListenPort{{Address: "127.0.0.1", Port: "22", PortScanSuccessOn: []string{"127.0.0.1"}}}}}},
|
||||
"packc": models.Package{Name: "packc", AffectedProcs: []models.AffectedProcess{{PID: "75", Name: "sshd", ListenPorts: []models.ListenPort{{Address: "127.0.0.1", Port: "22", PortScanSuccessOn: []string{"127.0.0.1"}}, {Address: "192.168.1.1", Port: "22", PortScanSuccessOn: []string{"192.168.1.1"}}}}}},
|
||||
"packd": models.Package{Name: "packd", AffectedProcs: []models.AffectedProcess{{PID: "75", Name: "sshd", ListenPorts: []models.ListenPort{{Address: "*", Port: "22", PortScanSuccessOn: []string{"127.0.0.1", "192.168.1.1"}}}}}},
|
||||
"packa": models.Package{Name: "packa", AffectedProcs: []models.AffectedProcess{{PID: "75", Name: "sshd", ListenPortStats: []models.PortStat{{BindAddress: "127.0.0.1", Port: "80", PortReachableTo: []string{}}}}}},
|
||||
"packb": models.Package{Name: "packb", AffectedProcs: []models.AffectedProcess{{PID: "75", Name: "sshd", ListenPortStats: []models.PortStat{{BindAddress: "127.0.0.1", Port: "22", PortReachableTo: []string{"127.0.0.1"}}}}}},
|
||||
"packc": models.Package{Name: "packc", AffectedProcs: []models.AffectedProcess{{PID: "75", Name: "sshd", ListenPortStats: []models.PortStat{{BindAddress: "127.0.0.1", Port: "22", PortReachableTo: []string{"127.0.0.1"}}, {BindAddress: "192.168.1.1", Port: "22", PortReachableTo: []string{"192.168.1.1"}}}}}},
|
||||
"packd": models.Package{Name: "packd", AffectedProcs: []models.AffectedProcess{{PID: "75", Name: "sshd", ListenPortStats: []models.PortStat{{BindAddress: "*", Port: "22", PortReachableTo: []string{"127.0.0.1", "192.168.1.1"}}}}}},
|
||||
},
|
||||
},
|
||||
}
|
||||
@@ -467,71 +467,26 @@ func Test_updatePortStatus(t *testing.T) {
|
||||
func Test_matchListenPorts(t *testing.T) {
|
||||
type args struct {
|
||||
listenIPPorts []string
|
||||
searchListenPort models.ListenPort
|
||||
searchListenPort models.PortStat
|
||||
}
|
||||
tests := []struct {
|
||||
name string
|
||||
args args
|
||||
expect []string
|
||||
}{
|
||||
{name: "open_empty", args: args{listenIPPorts: []string{}, searchListenPort: models.ListenPort{Address: "127.0.0.1", Port: "22"}}, expect: []string{}},
|
||||
{name: "port_empty", args: args{listenIPPorts: []string{"127.0.0.1:22"}, searchListenPort: models.ListenPort{}}, expect: []string{}},
|
||||
{name: "single_match", args: args{listenIPPorts: []string{"127.0.0.1:22"}, searchListenPort: models.ListenPort{Address: "127.0.0.1", Port: "22"}}, expect: []string{"127.0.0.1"}},
|
||||
{name: "no_match_address", args: args{listenIPPorts: []string{"127.0.0.1:22"}, searchListenPort: models.ListenPort{Address: "192.168.1.1", Port: "22"}}, expect: []string{}},
|
||||
{name: "no_match_port", args: args{listenIPPorts: []string{"127.0.0.1:22"}, searchListenPort: models.ListenPort{Address: "127.0.0.1", Port: "80"}}, expect: []string{}},
|
||||
{name: "asterisk_match", args: args{listenIPPorts: []string{"127.0.0.1:22", "127.0.0.1:80", "192.168.1.1:22"}, searchListenPort: models.ListenPort{Address: "*", Port: "22"}}, expect: []string{"127.0.0.1", "192.168.1.1"}},
|
||||
{name: "open_empty", args: args{listenIPPorts: []string{}, searchListenPort: models.PortStat{BindAddress: "127.0.0.1", Port: "22"}}, expect: []string{}},
|
||||
{name: "port_empty", args: args{listenIPPorts: []string{"127.0.0.1:22"}, searchListenPort: models.PortStat{}}, expect: []string{}},
|
||||
{name: "single_match", args: args{listenIPPorts: []string{"127.0.0.1:22"}, searchListenPort: models.PortStat{BindAddress: "127.0.0.1", Port: "22"}}, expect: []string{"127.0.0.1"}},
|
||||
{name: "no_match_address", args: args{listenIPPorts: []string{"127.0.0.1:22"}, searchListenPort: models.PortStat{BindAddress: "192.168.1.1", Port: "22"}}, expect: []string{}},
|
||||
{name: "no_match_port", args: args{listenIPPorts: []string{"127.0.0.1:22"}, searchListenPort: models.PortStat{BindAddress: "127.0.0.1", Port: "80"}}, expect: []string{}},
|
||||
{name: "asterisk_match", args: args{listenIPPorts: []string{"127.0.0.1:22", "127.0.0.1:80", "192.168.1.1:22"}, searchListenPort: models.PortStat{BindAddress: "*", Port: "22"}}, expect: []string{"127.0.0.1", "192.168.1.1"}},
|
||||
}
|
||||
|
||||
l := base{}
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
if match := l.findPortScanSuccessOn(tt.args.listenIPPorts, tt.args.searchListenPort); !reflect.DeepEqual(match, tt.expect) {
|
||||
t.Errorf("findPortScanSuccessOn() = %v, want %v", match, tt.expect)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func Test_base_parseListenPorts(t *testing.T) {
|
||||
tests := []struct {
|
||||
name string
|
||||
args string
|
||||
expect models.ListenPort
|
||||
}{{
|
||||
name: "empty",
|
||||
args: "",
|
||||
expect: models.ListenPort{
|
||||
Address: "",
|
||||
Port: "",
|
||||
},
|
||||
}, {
|
||||
name: "normal",
|
||||
args: "127.0.0.1:22",
|
||||
expect: models.ListenPort{
|
||||
Address: "127.0.0.1",
|
||||
Port: "22",
|
||||
},
|
||||
}, {
|
||||
name: "asterisk",
|
||||
args: "*:22",
|
||||
expect: models.ListenPort{
|
||||
Address: "*",
|
||||
Port: "22",
|
||||
},
|
||||
}, {
|
||||
name: "ipv6_loopback",
|
||||
args: "[::1]:22",
|
||||
expect: models.ListenPort{
|
||||
Address: "[::1]",
|
||||
Port: "22",
|
||||
},
|
||||
}}
|
||||
|
||||
l := base{}
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
if listenPort := l.parseListenPorts(tt.args); !reflect.DeepEqual(listenPort, tt.expect) {
|
||||
t.Errorf("base.parseListenPorts() = %v, want %v", listenPort, tt.expect)
|
||||
if match := l.findPortTestSuccessOn(tt.args.listenIPPorts, tt.args.searchListenPort); !reflect.DeepEqual(match, tt.expect) {
|
||||
t.Errorf("findPortTestSuccessOn() = %v, want %v", match, tt.expect)
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
@@ -241,7 +241,6 @@ func (o *debian) checkDeps() error {
|
||||
}
|
||||
|
||||
func (o *debian) preCure() error {
|
||||
o.log.Infof("Scanning in %s", o.getServerInfo().Mode)
|
||||
if err := o.detectIPAddr(); err != nil {
|
||||
o.log.Warnf("Failed to detect IP addresses: %s", err)
|
||||
o.warns = append(o.warns, err)
|
||||
@@ -277,6 +276,7 @@ func (o *debian) detectIPAddr() (err error) {
|
||||
}
|
||||
|
||||
func (o *debian) scanPackages() error {
|
||||
o.log.Infof("Scanning OS pkg in %s", o.getServerInfo().Mode)
|
||||
// collect the running kernel information
|
||||
release, version, err := o.runningKernel()
|
||||
if err != nil {
|
||||
@@ -968,7 +968,7 @@ func (o *debian) getCveIDsFromChangelog(
|
||||
|
||||
// If the version is not in changelog, return entire changelog to put into cache
|
||||
pack := o.Packages[name]
|
||||
pack.Changelog = models.Changelog{
|
||||
pack.Changelog = &models.Changelog{
|
||||
Contents: changelog,
|
||||
Method: models.FailedToFindVersionInChangelog,
|
||||
}
|
||||
@@ -1018,7 +1018,7 @@ func (o *debian) parseChangelog(changelog, name, ver string, confidence models.C
|
||||
if !found {
|
||||
if o.Distro.Family == config.Raspbian {
|
||||
pack := o.Packages[name]
|
||||
pack.Changelog = models.Changelog{
|
||||
pack.Changelog = &models.Changelog{
|
||||
Contents: strings.Join(buf, "\n"),
|
||||
Method: models.ChangelogLenientMatchStr,
|
||||
}
|
||||
@@ -1032,7 +1032,7 @@ func (o *debian) parseChangelog(changelog, name, ver string, confidence models.C
|
||||
}
|
||||
|
||||
pack := o.Packages[name]
|
||||
pack.Changelog = models.Changelog{
|
||||
pack.Changelog = &models.Changelog{
|
||||
Contents: "",
|
||||
Method: models.FailedToFindVersionInChangelog,
|
||||
}
|
||||
@@ -1046,7 +1046,7 @@ func (o *debian) parseChangelog(changelog, name, ver string, confidence models.C
|
||||
Method: confidence.DetectionMethod,
|
||||
}
|
||||
pack := o.Packages[name]
|
||||
pack.Changelog = clog
|
||||
pack.Changelog = &clog
|
||||
|
||||
cves := []DetectedCveID{}
|
||||
for _, id := range cveIDs {
|
||||
@@ -1294,15 +1294,20 @@ func (o *debian) dpkgPs() error {
|
||||
pidLoadedFiles[pid] = append(pidLoadedFiles[pid], ss...)
|
||||
}
|
||||
|
||||
pidListenPorts := map[string][]models.ListenPort{}
|
||||
pidListenPorts := map[string][]models.PortStat{}
|
||||
stdout, err = o.lsOfListen()
|
||||
if err != nil {
|
||||
return xerrors.Errorf("Failed to ls of: %w", err)
|
||||
}
|
||||
portPids := o.parseLsOf(stdout)
|
||||
for port, pids := range portPids {
|
||||
for ipPort, pids := range portPids {
|
||||
for _, pid := range pids {
|
||||
pidListenPorts[pid] = append(pidListenPorts[pid], o.parseListenPorts(port))
|
||||
portStat, err := models.NewPortStat(ipPort)
|
||||
if err != nil {
|
||||
o.log.Warnf("Failed to parse ip:port: %s, err: %+v", ipPort, err)
|
||||
continue
|
||||
}
|
||||
pidListenPorts[pid] = append(pidListenPorts[pid], *portStat)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1319,9 +1324,9 @@ func (o *debian) dpkgPs() error {
|
||||
procName = pidNames[pid]
|
||||
}
|
||||
proc := models.AffectedProcess{
|
||||
PID: pid,
|
||||
Name: procName,
|
||||
ListenPorts: pidListenPorts[pid],
|
||||
PID: pid,
|
||||
Name: procName,
|
||||
ListenPortStats: pidListenPorts[pid],
|
||||
}
|
||||
|
||||
for _, n := range pkgNames {
|
||||
|
||||
@@ -225,7 +225,7 @@ systemd (228-5) unstable; urgency=medium`,
|
||||
for i, tt := range tests {
|
||||
aCveIDs, aPack := d.getCveIDsFromChangelog(tt.in[2], tt.in[0], tt.in[1])
|
||||
if len(aCveIDs) != len(tt.cveIDs) {
|
||||
t.Errorf("[%d] Len of return array are'nt same. expected %#v, actual %#v", i, tt.cveIDs, aCveIDs)
|
||||
t.Errorf("[%d] Len of return array aren't same. expected %#v, actual %#v", i, tt.cveIDs, aCveIDs)
|
||||
t.Errorf(pp.Sprintf("%s", tt.in))
|
||||
continue
|
||||
}
|
||||
@@ -433,7 +433,7 @@ func TestGetChangelogCache(t *testing.T) {
|
||||
d := newDebian(config.ServerInfo{})
|
||||
actual := d.getChangelogCache(&meta, pack)
|
||||
if actual != "" {
|
||||
t.Errorf("Failed to get empty stirng from cache:")
|
||||
t.Errorf("Failed to get empty string from cache:")
|
||||
}
|
||||
|
||||
clog := "changelog-text"
|
||||
@@ -794,7 +794,7 @@ vlc (3.0.10-0+deb10u1) buster-security; urgency=medium`,
|
||||
},
|
||||
expect: expect{
|
||||
cveIDs: []DetectedCveID{{"CVE-2020-13428", models.ChangelogExactMatch}},
|
||||
pack: models.Package{Changelog: models.Changelog{
|
||||
pack: models.Package{Changelog: &models.Changelog{
|
||||
Contents: `vlc (3.0.11-0+deb10u1+rpt2) buster; urgency=medium
|
||||
|
||||
* Add MMAL patch 19
|
||||
@@ -837,7 +837,7 @@ vlc (3.0.11-0+deb10u1) buster-security; urgency=high
|
||||
},
|
||||
expect: expect{
|
||||
cveIDs: []DetectedCveID{},
|
||||
pack: models.Package{Changelog: models.Changelog{
|
||||
pack: models.Package{Changelog: &models.Changelog{
|
||||
Contents: `realvnc-vnc (6.7.2.42622) stable; urgency=low
|
||||
|
||||
* Debian package for VNC Server
|
||||
|
||||
@@ -69,9 +69,9 @@ func (o *bsd) checkDeps() error {
|
||||
}
|
||||
|
||||
func (o *bsd) 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)
|
||||
o.log.Warnf("Failed to detect IP addresses: %s", err)
|
||||
o.warns = append(o.warns, err)
|
||||
}
|
||||
// Ignore this error as it just failed to detect the IP addresses
|
||||
return nil
|
||||
@@ -115,6 +115,7 @@ func (l *base) parseIfconfig(stdout string) (ipv4Addrs []string, ipv6Addrs []str
|
||||
}
|
||||
|
||||
func (o *bsd) scanPackages() error {
|
||||
o.log.Infof("Scanning OS pkg in %s", o.getServerInfo().Mode)
|
||||
// collect the running kernel information
|
||||
release, version, err := o.runningKernel()
|
||||
if err != nil {
|
||||
|
||||
@@ -52,7 +52,7 @@ func detectRedhat(c config.ServerInfo) (bool, osTypeInterface) {
|
||||
|
||||
release := result[2]
|
||||
switch strings.ToLower(result[1]) {
|
||||
case "centos", "centos linux":
|
||||
case "centos", "centos linux", "centos stream":
|
||||
cent := newCentOS(c)
|
||||
cent.setDistro(config.CentOS, release)
|
||||
return true, cent
|
||||
@@ -193,27 +193,27 @@ func (o *redhatBase) postScan() error {
|
||||
}
|
||||
|
||||
func (o *redhatBase) detectIPAddr() (err error) {
|
||||
o.log.Infof("Scanning in %s", o.getServerInfo().Mode)
|
||||
o.ServerInfo.IPv4Addrs, o.ServerInfo.IPv6Addrs, err = o.ip()
|
||||
return err
|
||||
}
|
||||
|
||||
func (o *redhatBase) scanPackages() error {
|
||||
installed, err := o.scanInstalledPackages()
|
||||
func (o *redhatBase) scanPackages() (err error) {
|
||||
o.log.Infof("Scanning OS pkg in %s", o.getServerInfo().Mode)
|
||||
o.Packages, err = o.scanInstalledPackages()
|
||||
if err != nil {
|
||||
o.log.Errorf("Failed to scan installed packages: %s", err)
|
||||
return err
|
||||
return xerrors.Errorf("Failed to scan installed packages: %w", err)
|
||||
}
|
||||
o.Packages = installed
|
||||
|
||||
rebootRequired, err := o.rebootRequired()
|
||||
if o.EnabledDnfModules, err = o.detectEnabledDnfModules(); err != nil {
|
||||
return xerrors.Errorf("Failed to detect installed dnf modules: %w", err)
|
||||
}
|
||||
|
||||
o.Kernel.RebootRequired, err = o.rebootRequired()
|
||||
if err != nil {
|
||||
err = xerrors.Errorf("Failed to detect the kernel reboot required: %w", err)
|
||||
o.log.Warnf("err: %+v", err)
|
||||
o.warns = append(o.warns, err)
|
||||
// Only warning this error
|
||||
} else {
|
||||
o.Kernel.RebootRequired = rebootRequired
|
||||
}
|
||||
|
||||
if o.getServerInfo().Mode.IsOffline() {
|
||||
@@ -231,8 +231,7 @@ func (o *redhatBase) scanPackages() error {
|
||||
o.warns = append(o.warns, err)
|
||||
// Only warning this error
|
||||
} else {
|
||||
installed.MergeNewVersion(updatable)
|
||||
o.Packages = installed
|
||||
o.Packages.MergeNewVersion(updatable)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
@@ -261,7 +260,7 @@ func (o *redhatBase) scanInstalledPackages() (models.Packages, error) {
|
||||
Version: version,
|
||||
}
|
||||
|
||||
r := o.exec(o.rpmQa(o.Distro), noSudo)
|
||||
r := o.exec(o.rpmQa(), noSudo)
|
||||
if !r.isSuccess() {
|
||||
return nil, xerrors.Errorf("Scan packages failed: %s", r)
|
||||
}
|
||||
@@ -317,6 +316,11 @@ func (o *redhatBase) parseInstalledPackagesLine(line string) (models.Package, er
|
||||
return models.Package{},
|
||||
xerrors.Errorf("Failed to parse package line: %s", line)
|
||||
}
|
||||
if strings.HasSuffix(line, "Permission denied") {
|
||||
return models.Package{},
|
||||
xerrors.Errorf("Failed to parse package line: %s", line)
|
||||
}
|
||||
|
||||
ver := ""
|
||||
epoch := fields[1]
|
||||
if epoch == "0" || epoch == "(none)" {
|
||||
@@ -370,12 +374,6 @@ func (o *redhatBase) parseUpdatablePacksLines(stdout string) (models.Packages, e
|
||||
updatable := models.Packages{}
|
||||
lines := strings.Split(stdout, "\n")
|
||||
for _, line := range lines {
|
||||
// TODO remove
|
||||
// if strings.HasPrefix(line, "Obsoleting") ||
|
||||
// strings.HasPrefix(line, "Security:") {
|
||||
// // see https://github.com/future-architect/vuls/issues/165
|
||||
// continue
|
||||
// }
|
||||
if len(strings.TrimSpace(line)) == 0 {
|
||||
continue
|
||||
} else if strings.HasPrefix(line, "Loading") {
|
||||
@@ -491,15 +489,20 @@ func (o *redhatBase) yumPs() error {
|
||||
pidLoadedFiles[pid] = append(pidLoadedFiles[pid], ss...)
|
||||
}
|
||||
|
||||
pidListenPorts := map[string][]models.ListenPort{}
|
||||
pidListenPorts := map[string][]models.PortStat{}
|
||||
stdout, err = o.lsOfListen()
|
||||
if err != nil {
|
||||
return xerrors.Errorf("Failed to ls of: %w", err)
|
||||
return xerrors.Errorf("Failed to lsof: %w", err)
|
||||
}
|
||||
portPids := o.parseLsOf(stdout)
|
||||
for port, pids := range portPids {
|
||||
for ipPort, pids := range portPids {
|
||||
for _, pid := range pids {
|
||||
pidListenPorts[pid] = append(pidListenPorts[pid], o.parseListenPorts(port))
|
||||
portStat, err := models.NewPortStat(ipPort)
|
||||
if err != nil {
|
||||
o.log.Warnf("Failed to parse ip:port: %s, err: %+v", ipPort, err)
|
||||
continue
|
||||
}
|
||||
pidListenPorts[pid] = append(pidListenPorts[pid], *portStat)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -521,9 +524,9 @@ func (o *redhatBase) yumPs() error {
|
||||
procName = pidNames[pid]
|
||||
}
|
||||
proc := models.AffectedProcess{
|
||||
PID: pid,
|
||||
Name: procName,
|
||||
ListenPorts: pidListenPorts[pid],
|
||||
PID: pid,
|
||||
Name: procName,
|
||||
ListenPortStats: pidListenPorts[pid],
|
||||
}
|
||||
|
||||
for fqpn := range uniq {
|
||||
@@ -630,7 +633,7 @@ func (o *redhatBase) procPathToFQPN(execCommand string) (string, error) {
|
||||
}
|
||||
|
||||
func (o *redhatBase) getPkgName(paths []string) (pkgNames []string, err error) {
|
||||
cmd := o.rpmQf(o.Distro) + strings.Join(paths, " ")
|
||||
cmd := o.rpmQf() + strings.Join(paths, " ")
|
||||
r := o.exec(util.PrependProxyEnv(cmd), noSudo)
|
||||
if !r.isSuccess(0, 2, 4, 8) {
|
||||
return nil, xerrors.Errorf("Failed to rpm -qf: %s, cmd: %s", r, cmd)
|
||||
@@ -649,36 +652,74 @@ func (o *redhatBase) getPkgName(paths []string) (pkgNames []string, err error) {
|
||||
return pkgNames, nil
|
||||
}
|
||||
|
||||
func (o *redhatBase) rpmQa(distro config.Distro) string {
|
||||
func (o *redhatBase) rpmQa() string {
|
||||
const old = `rpm -qa --queryformat "%{NAME} %{EPOCH} %{VERSION} %{RELEASE} %{ARCH}\n"`
|
||||
const new = `rpm -qa --queryformat "%{NAME} %{EPOCHNUM} %{VERSION} %{RELEASE} %{ARCH}\n"`
|
||||
switch distro.Family {
|
||||
switch o.Distro.Family {
|
||||
case config.SUSEEnterpriseServer:
|
||||
if v, _ := distro.MajorVersion(); v < 12 {
|
||||
if v, _ := o.Distro.MajorVersion(); v < 12 {
|
||||
return old
|
||||
}
|
||||
return new
|
||||
default:
|
||||
if v, _ := distro.MajorVersion(); v < 6 {
|
||||
if v, _ := o.Distro.MajorVersion(); v < 6 {
|
||||
return old
|
||||
}
|
||||
return new
|
||||
}
|
||||
}
|
||||
|
||||
func (o *redhatBase) rpmQf(distro config.Distro) string {
|
||||
func (o *redhatBase) rpmQf() string {
|
||||
const old = `rpm -qf --queryformat "%{NAME} %{EPOCH} %{VERSION} %{RELEASE} %{ARCH}\n" `
|
||||
const new = `rpm -qf --queryformat "%{NAME} %{EPOCHNUM} %{VERSION} %{RELEASE} %{ARCH}\n" `
|
||||
switch distro.Family {
|
||||
switch o.Distro.Family {
|
||||
case config.SUSEEnterpriseServer:
|
||||
if v, _ := distro.MajorVersion(); v < 12 {
|
||||
if v, _ := o.Distro.MajorVersion(); v < 12 {
|
||||
return old
|
||||
}
|
||||
return new
|
||||
default:
|
||||
if v, _ := distro.MajorVersion(); v < 6 {
|
||||
if v, _ := o.Distro.MajorVersion(); v < 6 {
|
||||
return old
|
||||
}
|
||||
return new
|
||||
}
|
||||
}
|
||||
|
||||
func (o *redhatBase) detectEnabledDnfModules() ([]string, error) {
|
||||
switch o.Distro.Family {
|
||||
case config.RedHat, config.CentOS:
|
||||
//TODO OracleLinux
|
||||
default:
|
||||
return nil, nil
|
||||
}
|
||||
if v, _ := o.Distro.MajorVersion(); v < 8 {
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
cmd := `dnf --nogpgcheck --cacheonly --color=never --quiet module list --enabled`
|
||||
r := o.exec(util.PrependProxyEnv(cmd), noSudo)
|
||||
if !r.isSuccess() {
|
||||
if strings.Contains(r.Stdout, "Cache-only enabled but no cache") {
|
||||
return nil, xerrors.Errorf("sudo yum check-update to make local cache before scanning: %s", r)
|
||||
}
|
||||
return nil, xerrors.Errorf("Failed to dnf module list: %s", r)
|
||||
}
|
||||
return o.parseDnfModuleList(r.Stdout)
|
||||
}
|
||||
|
||||
func (o *redhatBase) parseDnfModuleList(stdout string) (labels []string, err error) {
|
||||
scanner := bufio.NewScanner(strings.NewReader(stdout))
|
||||
for scanner.Scan() {
|
||||
line := scanner.Text()
|
||||
if strings.HasPrefix(line, "Hint:") || !strings.Contains(line, "[i]") {
|
||||
continue
|
||||
}
|
||||
ss := strings.Fields(line)
|
||||
if len(ss) < 2 {
|
||||
continue
|
||||
}
|
||||
labels = append(labels, fmt.Sprintf("%s:%s", ss[0], ss[1]))
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
@@ -137,12 +137,13 @@ kernel-devel 0 2.6.32 695.20.3.el6 x86_64`,
|
||||
}
|
||||
|
||||
}
|
||||
func TestParseScanedPackagesLineRedhat(t *testing.T) {
|
||||
func TestParseInstalledPackagesLine(t *testing.T) {
|
||||
r := newRHEL(config.ServerInfo{})
|
||||
|
||||
var packagetests = []struct {
|
||||
in string
|
||||
pack models.Package
|
||||
err bool
|
||||
}{
|
||||
{
|
||||
"openssl 0 1.0.1e 30.el6.11 x86_64",
|
||||
@@ -151,6 +152,7 @@ func TestParseScanedPackagesLineRedhat(t *testing.T) {
|
||||
Version: "1.0.1e",
|
||||
Release: "30.el6.11",
|
||||
},
|
||||
false,
|
||||
},
|
||||
{
|
||||
"Percona-Server-shared-56 1 5.6.19 rel67.0.el6 x84_64",
|
||||
@@ -159,11 +161,23 @@ func TestParseScanedPackagesLineRedhat(t *testing.T) {
|
||||
Version: "1:5.6.19",
|
||||
Release: "rel67.0.el6",
|
||||
},
|
||||
false,
|
||||
},
|
||||
{
|
||||
"error: file /run/log/journal/346a500b7fb944199748954baca56086/system.journal: Permission denied",
|
||||
models.Package{},
|
||||
true,
|
||||
},
|
||||
}
|
||||
|
||||
for _, tt := range packagetests {
|
||||
p, _ := r.parseInstalledPackagesLine(tt.in)
|
||||
for i, tt := range packagetests {
|
||||
p, err := r.parseInstalledPackagesLine(tt.in)
|
||||
if err == nil && tt.err {
|
||||
t.Errorf("Expected err not occurred: %d", i)
|
||||
}
|
||||
if err != nil && !tt.err {
|
||||
t.Errorf("UnExpected err not occurred: %d", i)
|
||||
}
|
||||
if p.Name != tt.pack.Name {
|
||||
t.Errorf("name: expected %s, actual %s", tt.pack.Name, p.Name)
|
||||
}
|
||||
@@ -383,3 +397,44 @@ func TestParseNeedsRestarting(t *testing.T) {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func Test_redhatBase_parseDnfModuleList(t *testing.T) {
|
||||
type args struct {
|
||||
stdout string
|
||||
}
|
||||
tests := []struct {
|
||||
name string
|
||||
args args
|
||||
wantLabels []string
|
||||
wantErr bool
|
||||
}{
|
||||
{
|
||||
name: "Success",
|
||||
args: args{
|
||||
stdout: `Red Hat Enterprise Linux 8 for x86_64 - AppStream from RHUI (RPMs)
|
||||
Name Stream Profiles Summary
|
||||
virt rhel [d][e] common [d] Virtualization module
|
||||
nginx 1.14 [d][e] common [d] [i] nginx webserver
|
||||
|
||||
Hint: [d]efault, [e]nabled, [x]disabled, [i]nstalled`,
|
||||
},
|
||||
wantLabels: []string{
|
||||
"nginx:1.14",
|
||||
},
|
||||
wantErr: false,
|
||||
},
|
||||
}
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
o := &redhatBase{}
|
||||
gotLabels, err := o.parseDnfModuleList(tt.args.stdout)
|
||||
if (err != nil) != tt.wantErr {
|
||||
t.Errorf("redhatBase.parseDnfModuleList() error = %v, wantErr %v", err, tt.wantErr)
|
||||
return
|
||||
}
|
||||
if !reflect.DeepEqual(gotLabels, tt.wantLabels) {
|
||||
t.Errorf("redhatBase.parseDnfModuleList() = %v, want %v", gotLabels, tt.wantLabels)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
@@ -70,6 +70,9 @@ type osPackages struct {
|
||||
// installed source packages (Debian based only)
|
||||
SrcPackages models.SrcPackages
|
||||
|
||||
// enabled dnf modules or packages
|
||||
EnabledDnfModules []string
|
||||
|
||||
// unsecure packages
|
||||
VulnInfos models.VulnInfos
|
||||
|
||||
@@ -170,25 +173,20 @@ func PrintSSHableServerNames() bool {
|
||||
|
||||
// InitServers detect the kind of OS distribution of target servers
|
||||
func InitServers(timeoutSec int) error {
|
||||
// use global servers, errServers when scan containers
|
||||
servers, errServers = detectServerOSes(timeoutSec)
|
||||
if len(servers) == 0 {
|
||||
return xerrors.New("No scannable base servers")
|
||||
hosts, errHosts := detectServerOSes(timeoutSec)
|
||||
if len(hosts) == 0 {
|
||||
return xerrors.New("No scannable host OS")
|
||||
}
|
||||
containers, errContainers := detectContainerOSes(hosts, timeoutSec)
|
||||
|
||||
// scan additional servers
|
||||
var actives, inactives []osTypeInterface
|
||||
oks, errs := detectContainerOSes(timeoutSec)
|
||||
actives = append(actives, oks...)
|
||||
inactives = append(inactives, errs...)
|
||||
|
||||
if config.Conf.ContainersOnly {
|
||||
servers = actives
|
||||
errServers = inactives
|
||||
} else {
|
||||
servers = append(servers, actives...)
|
||||
errServers = append(errServers, inactives...)
|
||||
// set to pkg global variable
|
||||
for _, host := range hosts {
|
||||
if !host.getServerInfo().ContainersOnly {
|
||||
servers = append(servers, host)
|
||||
}
|
||||
}
|
||||
servers = append(servers, containers...)
|
||||
errServers = append(errHosts, errContainers...)
|
||||
|
||||
if len(servers) == 0 {
|
||||
return xerrors.New("No scannable servers")
|
||||
@@ -257,11 +255,11 @@ func detectServerOSes(timeoutSec int) (servers, errServers []osTypeInterface) {
|
||||
return
|
||||
}
|
||||
|
||||
func detectContainerOSes(timeoutSec int) (actives, inactives []osTypeInterface) {
|
||||
func detectContainerOSes(hosts []osTypeInterface, timeoutSec int) (actives, inactives []osTypeInterface) {
|
||||
util.Log.Info("Detecting OS of containers... ")
|
||||
osTypesChan := make(chan []osTypeInterface, len(servers))
|
||||
osTypesChan := make(chan []osTypeInterface, len(hosts))
|
||||
defer close(osTypesChan)
|
||||
for _, s := range servers {
|
||||
for _, s := range hosts {
|
||||
go func(s osTypeInterface) {
|
||||
defer func() {
|
||||
if p := recover(); p != nil {
|
||||
@@ -274,7 +272,7 @@ func detectContainerOSes(timeoutSec int) (actives, inactives []osTypeInterface)
|
||||
}
|
||||
|
||||
timeout := time.After(time.Duration(timeoutSec) * time.Second)
|
||||
for i := 0; i < len(servers); i++ {
|
||||
for i := 0; i < len(hosts); i++ {
|
||||
select {
|
||||
case res := <-osTypesChan:
|
||||
for _, osi := range res {
|
||||
@@ -492,7 +490,6 @@ func Scan(timeoutSec int) error {
|
||||
}
|
||||
}()
|
||||
|
||||
util.Log.Info("Scanning vulnerable OS packages...")
|
||||
scannedAt := time.Now()
|
||||
dir, err := EnsureResultDir(scannedAt)
|
||||
if err != nil {
|
||||
@@ -504,6 +501,12 @@ func Scan(timeoutSec int) error {
|
||||
return err
|
||||
}
|
||||
|
||||
for i, r := range results {
|
||||
if s, ok := config.Conf.Servers[r.ServerName]; ok {
|
||||
results[i] = r.ClearFields(s.IgnoredJSONKeys)
|
||||
}
|
||||
}
|
||||
|
||||
return writeScanResults(dir, results)
|
||||
}
|
||||
|
||||
@@ -563,6 +566,10 @@ func ViaHTTP(header http.Header, body string) (models.ScanResult, error) {
|
||||
osType = ¢os{
|
||||
redhatBase: redhatBase{base: base},
|
||||
}
|
||||
case config.Oracle:
|
||||
osType = &oracle{
|
||||
redhatBase: redhatBase{base: base},
|
||||
}
|
||||
case config.Amazon:
|
||||
osType = &amazon{
|
||||
redhatBase: redhatBase{base: base},
|
||||
@@ -618,7 +625,7 @@ func setupChangelogCache() error {
|
||||
// GetScanResults returns ScanResults from
|
||||
func GetScanResults(scannedAt time.Time, timeoutSec int) (results models.ScanResults, err error) {
|
||||
parallelExec(func(o osTypeInterface) (err error) {
|
||||
if !(config.Conf.LibsOnly || config.Conf.WordPressOnly) {
|
||||
if o.getServerInfo().Module.IsScanOSPkg() {
|
||||
if err = o.preCure(); err != nil {
|
||||
return err
|
||||
}
|
||||
@@ -629,14 +636,20 @@ func GetScanResults(scannedAt time.Time, timeoutSec int) (results models.ScanRes
|
||||
return err
|
||||
}
|
||||
}
|
||||
if err = o.scanWordPress(); err != nil {
|
||||
return xerrors.Errorf("Failed to scan WordPress: %w", err)
|
||||
if o.getServerInfo().Module.IsScanPort() {
|
||||
if err = o.scanPorts(); err != nil {
|
||||
return xerrors.Errorf("Failed to scan Ports: %w", err)
|
||||
}
|
||||
}
|
||||
if err = o.scanLibraries(); err != nil {
|
||||
return xerrors.Errorf("Failed to scan Library: %w", err)
|
||||
if o.getServerInfo().Module.IsScanWordPress() {
|
||||
if err = o.scanWordPress(); err != nil {
|
||||
return xerrors.Errorf("Failed to scan WordPress: %w", err)
|
||||
}
|
||||
}
|
||||
if err = o.scanPorts(); err != nil {
|
||||
return xerrors.Errorf("Failed to scan Ports: %w", err)
|
||||
if o.getServerInfo().Module.IsScanLockFile() {
|
||||
if err = o.scanLibraries(); err != nil {
|
||||
return xerrors.Errorf("Failed to scan Library: %w", err)
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}, timeoutSec)
|
||||
@@ -649,6 +662,7 @@ func GetScanResults(scannedAt time.Time, timeoutSec int) (results models.ScanRes
|
||||
|
||||
for _, s := range append(servers, errServers...) {
|
||||
r := s.convertToModel()
|
||||
checkEOL(&r)
|
||||
r.ScannedAt = scannedAt
|
||||
r.ScannedVersion = config.Version
|
||||
r.ScannedRevision = config.Revision
|
||||
@@ -666,6 +680,42 @@ func GetScanResults(scannedAt time.Time, timeoutSec int) (results models.ScanRes
|
||||
return results, nil
|
||||
}
|
||||
|
||||
func checkEOL(r *models.ScanResult) {
|
||||
switch r.Family {
|
||||
case config.ServerTypePseudo, config.Raspbian:
|
||||
return
|
||||
}
|
||||
|
||||
eol, found := config.GetEOL(r.Family, r.Release)
|
||||
if !found {
|
||||
r.Warnings = append(r.Warnings,
|
||||
fmt.Sprintf("Failed to check EOL. Register the issue to https://github.com/future-architect/vuls/issues with the information in `Family: %s Release: %s`",
|
||||
r.Family, r.Release))
|
||||
return
|
||||
}
|
||||
|
||||
now := time.Now()
|
||||
if eol.IsStandardSupportEnded(now) {
|
||||
r.Warnings = append(r.Warnings, "Standard OS support is EOL(End-of-Life). Purchase extended support if available or Upgrading your OS is strongly recommended.")
|
||||
if eol.ExtendedSupportUntil.IsZero() {
|
||||
return
|
||||
}
|
||||
if !eol.IsExtendedSuppportEnded(now) {
|
||||
r.Warnings = append(r.Warnings,
|
||||
fmt.Sprintf("Extended support available until %s. Check the vendor site.",
|
||||
eol.ExtendedSupportUntil.Format("2006-01-02")))
|
||||
} else {
|
||||
r.Warnings = append(r.Warnings,
|
||||
"Extended support is also EOL. There are many Vulnerabilities that are not detected, Upgrading your OS strongly recommended.")
|
||||
}
|
||||
} else if !eol.StandardSupportUntil.IsZero() &&
|
||||
now.AddDate(0, 3, 0).After(eol.StandardSupportUntil) {
|
||||
r.Warnings = append(r.Warnings,
|
||||
fmt.Sprintf("Standard OS support will be end in 3 months. EOL date: %s",
|
||||
eol.StandardSupportUntil.Format("2006-01-02")))
|
||||
}
|
||||
}
|
||||
|
||||
func writeScanResults(jsonDir string, results models.ScanResults) error {
|
||||
config.Conf.FormatJSON = true
|
||||
ws := []report.ResultWriter{
|
||||
@@ -673,7 +723,7 @@ func writeScanResults(jsonDir string, results models.ScanResults) error {
|
||||
}
|
||||
for _, w := range ws {
|
||||
if err := w.Write(results...); err != nil {
|
||||
return xerrors.Errorf("Failed to write summary report: %s", err)
|
||||
return xerrors.Errorf("Failed to write summary: %s", err)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -115,6 +115,7 @@ func (o *suse) checkIfSudoNoPasswd() error {
|
||||
}
|
||||
|
||||
func (o *suse) scanPackages() error {
|
||||
o.log.Infof("Scanning OS pkg in %s", o.getServerInfo().Mode)
|
||||
installed, err := o.scanInstalledPackages()
|
||||
if err != nil {
|
||||
o.log.Errorf("Failed to scan installed packages: %s", err)
|
||||
|
||||
1
server/empty.go
Normal file
1
server/empty.go
Normal file
@@ -0,0 +1 @@
|
||||
package server
|
||||
@@ -1,3 +1,5 @@
|
||||
// +build !scanner
|
||||
|
||||
package server
|
||||
|
||||
import (
|
||||
@@ -10,7 +12,6 @@ import (
|
||||
"time"
|
||||
|
||||
c "github.com/future-architect/vuls/config"
|
||||
"github.com/future-architect/vuls/libmanager"
|
||||
"github.com/future-architect/vuls/models"
|
||||
"github.com/future-architect/vuls/report"
|
||||
"github.com/future-architect/vuls/scan"
|
||||
@@ -22,11 +23,12 @@ type VulsHandler struct {
|
||||
DBclient report.DBClient
|
||||
}
|
||||
|
||||
func (h VulsHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) {
|
||||
// ServeHTTP is http handler
|
||||
func (h VulsHandler) ServeHTTP(w http.ResponseWriter, req *http.Request) {
|
||||
var err error
|
||||
result := models.ScanResult{ScannedCves: models.VulnInfos{}}
|
||||
|
||||
contentType := r.Header.Get("Content-Type")
|
||||
contentType := req.Header.Get("Content-Type")
|
||||
mediatype, _, err := mime.ParseMediaType(contentType)
|
||||
if err != nil {
|
||||
util.Log.Error(err)
|
||||
@@ -35,18 +37,18 @@ func (h VulsHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) {
|
||||
}
|
||||
|
||||
if mediatype == "application/json" {
|
||||
if err = json.NewDecoder(r.Body).Decode(&result); err != nil {
|
||||
if err = json.NewDecoder(req.Body).Decode(&result); err != nil {
|
||||
util.Log.Error(err)
|
||||
http.Error(w, "Invalid JSON", http.StatusBadRequest)
|
||||
return
|
||||
}
|
||||
} else if mediatype == "text/plain" {
|
||||
buf := new(bytes.Buffer)
|
||||
if _, err := io.Copy(buf, r.Body); err != nil {
|
||||
if _, err := io.Copy(buf, req.Body); err != nil {
|
||||
http.Error(w, err.Error(), http.StatusBadRequest)
|
||||
return
|
||||
}
|
||||
if result, err = scan.ViaHTTP(r.Header, buf.String()); err != nil {
|
||||
if result, err = scan.ViaHTTP(req.Header, buf.String()); err != nil {
|
||||
util.Log.Error(err)
|
||||
http.Error(w, err.Error(), http.StatusBadRequest)
|
||||
return
|
||||
@@ -57,16 +59,14 @@ func (h VulsHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) {
|
||||
return
|
||||
}
|
||||
|
||||
nCVEs, err := libmanager.DetectLibsCves(&result)
|
||||
if err != nil {
|
||||
util.Log.Error("Failed to fill with Library dependency: %w", err)
|
||||
if err := report.DetectPkgCves(h.DBclient, &result); err != nil {
|
||||
util.Log.Errorf("Failed to detect Pkg CVE: %+v", err)
|
||||
http.Error(w, err.Error(), http.StatusServiceUnavailable)
|
||||
return
|
||||
}
|
||||
util.Log.Infof("%s: %d CVEs are detected with Library",
|
||||
result.FormatServerName(), nCVEs)
|
||||
|
||||
if err := report.FillCveInfo(h.DBclient, &result, []string{}, true); err != nil {
|
||||
util.Log.Error(err)
|
||||
if err := report.FillCveInfo(h.DBclient, &result); err != nil {
|
||||
util.Log.Errorf("Failed to fill CVE detailed info: %+v", err)
|
||||
http.Error(w, err.Error(), http.StatusServiceUnavailable)
|
||||
return
|
||||
}
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user