From 99c65eff48b1d92b9ccdb2220a569ff8b5317e15 Mon Sep 17 00:00:00 2001 From: kazuminn Date: Mon, 8 Apr 2019 17:27:44 +0900 Subject: [PATCH] feat(scan): WordPress Vulnerability Scan (core, plugin, theme) (#769) https://github.com/future-architect/vuls/pull/769 --- .travis.yml | 2 +- GNUmakefile | 5 +- Gopkg.lock | 90 +++--- Gopkg.toml | 4 + README.md | 25 +- cache/bolt.go | 12 +- commands/discover.go | 7 + commands/util.go | 3 +- config/config.go | 128 +++++---- config/jsonloader.go | 4 +- config/tomlloader.go | 36 ++- .../owasp-dependency-check/parser/parser.go | 4 +- exploit/exploit.go | 4 +- exploit/util.go | 13 +- gost/debian.go | 2 +- gost/gost.go | 4 +- gost/gost_test.go | 14 +- gost/redhat.go | 4 +- gost/util.go | 17 +- models/cvecontents.go | 7 + models/packages.go | 4 +- models/scanresults.go | 84 ++++-- models/scanresults_test.go | 26 +- models/vulninfos.go | 104 +++++-- models/vulninfos_test.go | 20 +- models/wordpress.go | 88 ++++++ oval/debian_test.go | 4 +- oval/oval.go | 18 +- oval/redhat_test.go | 4 +- oval/util.go | 24 +- oval/util_test.go | 4 +- report/azureblob.go | 9 +- report/cve_client.go | 24 +- report/db_client.go | 14 +- report/email.go | 7 +- report/http.go | 5 +- report/localfile.go | 24 +- report/report.go | 85 +++--- report/s3.go | 13 +- report/saas.go | 13 +- report/slack.go | 60 ++-- report/syslog.go | 5 +- report/syslog_test.go | 16 +- report/telegram.go | 3 +- report/tui.go | 41 ++- report/util.go | 78 +++-- report/util_test.go | 20 +- scan/alpine.go | 12 +- scan/amazon.go | 7 +- scan/base.go | 188 ++++++++++-- scan/debian.go | 63 ++-- scan/executil.go | 21 +- scan/freebsd.go | 20 +- scan/redhatbase.go | 59 ++-- scan/rhel.go | 5 +- scan/serverapi.go | 52 ++-- scan/suse.go | 5 +- wordpress/wordpress.go | 271 ++++++++++++++++++ wordpress/wordpress_test.go | 1 + 59 files changed, 1284 insertions(+), 602 deletions(-) create mode 100644 models/wordpress.go create mode 100644 wordpress/wordpress.go create mode 100644 wordpress/wordpress_test.go diff --git a/.travis.yml b/.travis.yml index 2d5cc98a..99f11e16 100644 --- a/.travis.yml +++ b/.travis.yml @@ -1,7 +1,7 @@ language: go go: - - "1.11.x" + - "1.12.x" after_success: - test -n "$TRAVIS_TAG" && curl -sL https://git.io/goreleaser | bash diff --git a/GNUmakefile b/GNUmakefile index 4960d1c1..9a81e057 100644 --- a/GNUmakefile +++ b/GNUmakefile @@ -5,7 +5,7 @@ install \ all \ vendor \ - lint \ + lint \ vet \ fmt \ fmtcheck \ @@ -33,6 +33,9 @@ depup: dep ensure -update -v build: main.go dep pretest + go build -a -ldflags "$(LDFLAGS)" -o vuls $< + +b: main.go dep pretest go build -ldflags "$(LDFLAGS)" -o vuls $< install: main.go dep pretest diff --git a/Gopkg.lock b/Gopkg.lock index 6bd9273d..a6ec98d6 100644 --- a/Gopkg.lock +++ b/Gopkg.lock @@ -2,23 +2,23 @@ [[projects]] - digest = "1:bfd9c4a3ff18fa3715d243c6dff6465eaf3c9c8cd0a61d81ce74ca0f6eb239f9" + digest = "1:ac06a4a43f8618111e6f37d22280a4d58d6d09ca621437cce93af7623c2117d3" name = "contrib.go.opencensus.io/exporter/ocagent" packages = ["."] pruneopts = "UT" - revision = "902c0ccba68df93f7fefbe7e7c6f16be33108b40" - version = "v0.4.9" + revision = "bc69a60230000ba2fe80ce3aba578b8cc6ec7587" + version = "v0.4.11" [[projects]] - digest = "1:6d0c80d0c0ca37ad7b3f46671a586b319090ac573dcb6229a567f0ac56bd943b" + digest = "1:e5385be33ddb613a47d6648aa2cde03750b1d80a5e6800b809cab755ebfcc188" name = "github.com/Azure/azure-sdk-for-go" packages = [ "storage", "version", ] pruneopts = "UT" - revision = "f0ff339f1297d3374fc36bcbfc7d1bbba045d992" - version = "v26.7.0" + revision = "d659f2a91175cac99aa5627d09b83026eacc978d" + version = "v27.0.0" [[projects]] digest = "1:b34c165560597b272b6ccf89dd3175eb7717e6247ce5ece215fa1905a7e19c22" @@ -60,7 +60,7 @@ version = "v9" [[projects]] - digest = "1:7f1dc60a57b23f6656f28d46513e13e75d24c52e256864d332ff44ca2e39d751" + digest = "1:ced9b678d6fdca06376ad5a1203bf7ab164a500268714fedaebdb3b9e90e6530" name = "github.com/aws/aws-sdk-go" packages = [ "aws", @@ -99,8 +99,8 @@ "service/sts", ] pruneopts = "UT" - revision = "bae168494294b48b3fc9d6722a711246a8543034" - version = "v1.19.2" + revision = "56c1def75689cceec1fa6f14c2eedb4b798827f9" + version = "v1.19.11" [[projects]] digest = "1:0f98f59e9a2f4070d66f0c9c39561f68fcd1dc837b22a852d28d0003aebd1b1e" @@ -150,12 +150,12 @@ version = "v3.2.0" [[projects]] - digest = "1:865079840386857c809b72ce300be7580cb50d3d3129ce11bf9aa6ca2bc1934a" + digest = "1:938a2672d6ebbb7f7bc63eee3e4b9464c16ffcf77ec8913d3edbf32b4e3984dd" name = "github.com/fatih/color" packages = ["."] pruneopts = "UT" - revision = "5b77d2a35fb0ede96d138fc9a99f5c9b6aef11b4" - version = "v1.7.0" + revision = "570b54cabe6b8eb0bc2dfce68d964677d63b5260" + version = "v1.5.0" [[projects]] digest = "1:abeb38ade3f32a92943e5be54f55ed6d6e3b6602761d74b4aab4c9dd45c18abd" @@ -362,12 +362,12 @@ version = "v0.4.0" [[projects]] - digest = "1:3569ef9147aa8c258a306facf2f62eaff98ee7a1acd52579b4bf9632f001386b" + digest = "1:86c75d55fde56a814f69515d092963634d6f5f6bc1c3e9091b01fbe373a9546e" name = "github.com/k0kubun/pp" packages = ["."] pruneopts = "UT" - revision = "1fdb803f401223aa9a9b7df66b7444d304f50bb0" - version = "v3.0.0" + revision = "3d73dea227e0711e38b911ffa6fbafc8ff6b2991" + version = "v3.0.1" [[projects]] branch = "master" @@ -500,12 +500,12 @@ version = "v1.1.0" [[projects]] - digest = "1:c658e84ad3916da105a761660dcaeb01e63416c8ec7bc62256a9b411a05fcd67" + digest = "1:2fa7b0155cd54479a755c629de26f888a918e13f8857a2c442205d825368e084" name = "github.com/mattn/go-colorable" packages = ["."] pruneopts = "UT" - revision = "167de6bfdfba052fa6b2d3664c8f5272e23c9072" - version = "v0.0.9" + revision = "3a70a971f94a22f2fa562ffcc7a0eb45f5daf045" + version = "v0.1.1" [[projects]] digest = "1:e150b5fafbd7607e2d638e4e5cf43aa4100124e5593385147b0a74e2733d8b0d" @@ -611,12 +611,12 @@ version = "v0.2.15" [[projects]] - digest = "1:95741de3af260a92cc5c7f3f3061e85273f5a81b5db20d4bd68da74bd521675e" + digest = "1:e0f50a07c0def90588d69f77178712c6fdc67eb6576365f551cce98b44b501bf" name = "github.com/pelletier/go-toml" packages = ["."] pruneopts = "UT" - revision = "c01d1270ff3e442a8a57cddc1c92dc1138598194" - version = "v1.2.0" + revision = "63909f0a90ab0f36909e8e044e46ace10cf13ba2" + version = "v1.3.0" [[projects]] digest = "1:cf31692c14422fa27c83a05292eb5cbe0fb2775972e8f1f8446a71549bd8980b" @@ -644,11 +644,11 @@ [[projects]] branch = "master" - digest = "1:35e32faf05a9a694fd831ac0ca6db9bc785db2863cf9352965a06e91e40260b7" + digest = "1:cabe9ec8289704e71e45138c18ad90275bce25a8d61145c4130b55d9d9a6d6dc" name = "github.com/sirupsen/logrus" packages = ["."] pruneopts = "UT" - revision = "1115b87d621f90f39ac3d2bd230797e35c1d478d" + revision = "9b3cdde74fbe9443d704467498a7dcb61a79de9b" [[projects]] digest = "1:bb495ec276ab82d3dd08504bbc0594a65de8c3b22c6f2aaa92d05b73fbf3a82e" @@ -718,13 +718,15 @@ revision = "0a0be1dd9d0855b50be0be5a10ad3085382b6d59" [[projects]] - digest = "1:006e728c96e02ac83ee246183a4b87b44b21e67e004d3a06edd891583a4bf197" + digest = "1:4c93890bbbb5016505e856cb06b5c5a2ff5b7217584d33f2a9071ebef4b5d473" name = "go.opencensus.io" packages = [ ".", - "exemplar", "internal", "internal/tagencoding", + "metric/metricdata", + "metric/metricproducer", + "plugin/ocgrpc", "plugin/ochttp", "plugin/ochttp/propagation/b3", "plugin/ochttp/propagation/tracecontext", @@ -739,8 +741,8 @@ "trace/tracestate", ] pruneopts = "UT" - revision = "3b8e2721f2c3c01fa1bf4a2e455874e7b8319cd7" - version = "v0.19.2" + revision = "43463a80402d8447b7fce0d2c58edf1687ff0b58" + version = "v0.19.3" [[projects]] branch = "master" @@ -758,11 +760,11 @@ "ssh/terminal", ] pruneopts = "UT" - revision = "a5d413f7728c81fb97d96a2b722368945f651e78" + revision = "38d8ce5564a5b71b2e3a00553993f1b9a7ae852f" [[projects]] branch = "master" - digest = "1:8b5b5fb6f7a8670347ad0c21555752ef43b7e8bc95e7b60de621e88636a3c2a8" + digest = "1:df69c1d5940da38566e9d90689ae308d78d5d510a72466384a6d8eca40bca80f" name = "golang.org/x/net" packages = [ "context", @@ -776,7 +778,7 @@ "trace", ] pruneopts = "UT" - revision = "710a502c58a2a4b2d7417a4e24ccb96d8dc13fa3" + revision = "eb5bcb51f2a31c7d5141d810b70815c05d9c9146" [[projects]] branch = "master" @@ -787,7 +789,7 @@ "internal", ] pruneopts = "UT" - revision = "c85d3e98c914e3a33234ad863dcbff5dbc425bb8" + revision = "9f3314589c9a9136388751d9adae6b0ed400978a" [[projects]] branch = "master" @@ -799,7 +801,7 @@ [[projects]] branch = "master" - digest = "1:78ed3d0c6d5b4996ef2867e280f39ea7b44438558c6331721740ab9061db532c" + digest = "1:580bd6f5fcc95d5d3dfa5f6916ccfaac7abb1722b1bfc7f042603e167428a802" name = "golang.org/x/sys" packages = [ "cpu", @@ -807,7 +809,7 @@ "windows", ] pruneopts = "UT" - revision = "f49334f85ddcf0f08d7fb6dd7363e9e6d6b777eb" + revision = "4b34438f7a67ee5f45cc6132e2bad873a20324e9" [[projects]] digest = "1:a2ab62866c75542dd18d2b069fec854577a20211d7c0ea6ae746072a1dccdd18" @@ -832,6 +834,17 @@ revision = "f21a4dfb5e38f5895301dc265a8def02365cc3d0" version = "v0.3.0" +[[projects]] + branch = "master" + digest = "1:363686890941480b8d1a0421b9e538cf55feaf9c7967bf4c809150b36138fe1c" + name = "golang.org/x/xerrors" + packages = [ + ".", + "internal", + ] + pruneopts = "UT" + revision = "d61658bd2e18010be0e21349cc92b1b706e35146" + [[projects]] digest = "1:5f003878aabe31d7f6b842d4de32b41c46c214bb629bb485387dbcce1edf5643" name = "google.golang.org/api" @@ -867,7 +880,7 @@ "protobuf/field_mask", ] pruneopts = "UT" - revision = "d831d65fe17df2e52bcc4316d4a9f7a418701f43" + revision = "64821d5d210748c883cd2b809589555ae4654203" [[projects]] digest = "1:c00eb80d7b152379c3e94c38d82b29deca98b1d0f53e4e20362589b7fcbffa07" @@ -934,12 +947,12 @@ version = "v2.0.6" [[projects]] - digest = "1:865079840386857c809b72ce300be7580cb50d3d3129ce11bf9aa6ca2bc1934a" + digest = "1:938a2672d6ebbb7f7bc63eee3e4b9464c16ffcf77ec8913d3edbf32b4e3984dd" name = "gopkg.in/fatih/color.v1" packages = ["."] pruneopts = "UT" - revision = "5b77d2a35fb0ede96d138fc9a99f5c9b6aef11b4" - version = "v1.7.0" + revision = "570b54cabe6b8eb0bc2dfce68d964677d63b5260" + version = "v1.5.0" [[projects]] digest = "1:c658e84ad3916da105a761660dcaeb01e63416c8ec7bc62256a9b411a05fcd67" @@ -992,6 +1005,7 @@ "github.com/cenkalti/backoff", "github.com/google/subcommands", "github.com/gosuri/uitable", + "github.com/hashicorp/go-version", "github.com/hashicorp/uuid", "github.com/howeyc/gopass", "github.com/jroimartin/gocui", @@ -1014,12 +1028,12 @@ "github.com/nlopes/slack", "github.com/olekukonko/tablewriter", "github.com/parnurzeal/gorequest", - "github.com/pkg/errors", "github.com/rifflock/lfshook", "github.com/sirupsen/logrus", "golang.org/x/crypto/ssh", "golang.org/x/crypto/ssh/agent", "golang.org/x/oauth2", + "golang.org/x/xerrors", ] solver-name = "gps-cdcl" solver-version = 1 diff --git a/Gopkg.toml b/Gopkg.toml index 2c86e60a..6edfe104 100644 --- a/Gopkg.toml +++ b/Gopkg.toml @@ -41,3 +41,7 @@ [prune] go-tests = true unused-packages = true + +[[constraint]] + branch = "master" + name = "golang.org/x/xerrors" diff --git a/README.md b/README.md index f62e3fab..3bd7878e 100644 --- a/README.md +++ b/README.md @@ -75,6 +75,7 @@ Vuls uses multiple vulnerability databases - [Exploit Database](https://www.exploit-db.com/) - [US-CERT](https://www.us-cert.gov/ncas/alerts) - [JPCERT](http://www.jpcert.or.jp/at/2019.html) +- [WPVulnDB](https://wpvulndb.com/api) - Changelog ## Fast scan and Deep scan @@ -102,16 +103,23 @@ Vuls uses multiple vulnerability databases it's possible to create a list of all vulnerabilities that need to be fixed. - Sometimes load on the scan target server -## [Remote scan and Local scan](https://vuls.io/docs/en/architecture-remote-local.html) +## [Remote scan, Local scan mode, Server mode](https://vuls.io/docs/en/architecture-remote-local.html) -[Remote Scan](https://vuls.io/docs/en/architecture-remote-scan.html) +[Remote scan mode](https://vuls.io/docs/en/architecture-remote-scan.html) - User is required to only set up one machine that is connected to other target servers via SSH -[Local Scan](https://vuls.io/docs/en/architecture-local-scan.html) +[Local scan mode](https://vuls.io/docs/en/architecture-local-scan.html) - If you don't want the central Vuls server to connect to each server by SSH, you can use Vuls in the Local Scan mode. +[Server mode](https://vuls.io/docs/en/usage-server.html) + +- First, start Vuls in server mode and listen as an HTTP server. +- Start Vuls in server mode and listen as an HTTP server. +- Next, issue a command on the scan target server to collect software information. Then send the result to Vuls Server via HTTP. You receive the scan results as JSON format. +- No SSH needed, No Scanner needed. Only issuing Linux commands directory on the scan tareget server. + ## **Dynamic** Analysis - It is possible to acquire the state of the server by connecting via SSH and executing the command. @@ -120,9 +128,14 @@ Vuls uses multiple vulnerability databases ## Scan vulnerabilities of non-OS packages - [Common Platform Enumeration (CPE) based Scan](https://vuls.io/docs/en/usage-scan-non-os-packages.html#how-to-search-cpe-name-by-software-name) - - NW equipment, middleware, programming language libraries and framework for vulnerability -- Integrate with [GitHub Security Alerts](https://vuls.io/docs/en/usage-scan-non-os-packages.html#usage-integrate-with-github-security-alerts) -- Integrate with [OWASP Dependency Check](https://vuls.io/docs/en/usage-scan-non-os-packages.html#usage-integrate-with-owasp-dependency-check-to-automatic-update-when-the-libraries-are-updated-experimental) + - Scan middleware, programming language libraries and framework for vulnerability + - Support software registered in CPE + +# Integration + +- [GitHub Security Alerts](https://vuls.io/docs/en/usage-scan-non-os-packages.html#usage-integrate-with-github-security-alerts) +- [OWASP Dependency Check](https://vuls.io/docs/en/usage-scan-non-os-packages.html#usage-integrate-with-owasp-dependency-check-to-automatic-update-when-the-libraries-are-updated-experimental) +- [WordPress](https://vuls.io/docs/en/usage-scan-wordpress.html) ## MISC diff --git a/cache/bolt.go b/cache/bolt.go index bb8fcf91..51a328d8 100644 --- a/cache/bolt.go +++ b/cache/bolt.go @@ -19,12 +19,12 @@ package cache import ( "encoding/json" - "fmt" "time" "github.com/boltdb/bolt" "github.com/future-architect/vuls/util" "github.com/sirupsen/logrus" + "golang.org/x/xerrors" ) // Bolt holds a pointer of bolt.DB @@ -69,7 +69,7 @@ func (b *Bolt) createBucketIfNotExists(name string) error { return b.db.Update(func(tx *bolt.Tx) error { _, err := tx.CreateBucketIfNotExists([]byte(name)) if err != nil { - return fmt.Errorf("Failed to create bucket: %s", err) + return xerrors.Errorf("Failed to create bucket: %w", err) } return nil }) @@ -98,7 +98,7 @@ func (b Bolt) RefreshMeta(meta Meta) error { meta.CreatedAt = time.Now() jsonBytes, err := json.Marshal(meta) if err != nil { - return fmt.Errorf("Failed to marshal to JSON: %s", err) + return xerrors.Errorf("Failed to marshal to JSON: %w", err) } return b.db.Update(func(tx *bolt.Tx) error { bkt := tx.Bucket([]byte(metabucket)) @@ -114,7 +114,7 @@ func (b Bolt) RefreshMeta(meta Meta) error { func (b Bolt) EnsureBuckets(meta Meta) error { jsonBytes, err := json.Marshal(meta) if err != nil { - return fmt.Errorf("Failed to marshal to JSON: %s", err) + return xerrors.Errorf("Failed to marshal to JSON: %w", err) } return b.db.Update(func(tx *bolt.Tx) error { b.Log.Debugf("Put to meta: %s", meta.Name) @@ -163,7 +163,7 @@ func (b Bolt) GetChangelog(servername, packName string) (changelog string, err e err = b.db.View(func(tx *bolt.Tx) error { bkt := tx.Bucket([]byte(servername)) if bkt == nil { - return fmt.Errorf("Failed to get Bucket: %s", servername) + return xerrors.Errorf("Failed to get Bucket: %s", servername) } v := bkt.Get([]byte(packName)) if v == nil { @@ -181,7 +181,7 @@ func (b Bolt) PutChangelog(servername, packName, changelog string) error { return b.db.Update(func(tx *bolt.Tx) error { bkt := tx.Bucket([]byte(servername)) if bkt == nil { - return fmt.Errorf("Failed to get Bucket: %s", servername) + return xerrors.Errorf("Failed to get Bucket: %s", servername) } return bkt.Put([]byte(packName), []byte(changelog)) }) diff --git a/commands/discover.go b/commands/discover.go index d7a4746b..4d159901 100644 --- a/commands/discover.go +++ b/commands/discover.go @@ -223,6 +223,13 @@ host = "{{$ip}}" #[servers.{{index $names $i}}.githubs."owner/repo"] #token = "yourToken" +#[servers.{{index $names $i}}.wordpress] +#cmdPath = "/usr/local/bin/wp" +#osUser = "wordpress" +#docRoot = "/path/to/DocumentRoot/" +#wpVulnDBToken = "xxxxTokenxxxx" +#ignoreInactive = true + #[servers.{{index $names $i}}.optional] #key = "value1" diff --git a/commands/util.go b/commands/util.go index 23802f0e..b22fe392 100644 --- a/commands/util.go +++ b/commands/util.go @@ -24,6 +24,7 @@ import ( "github.com/howeyc/gopass" homedir "github.com/mitchellh/go-homedir" + "golang.org/x/xerrors" ) func getPasswd(prompt string) (string, error) { @@ -31,7 +32,7 @@ func getPasswd(prompt string) (string, error) { fmt.Print(prompt) pass, err := gopass.GetPasswdMasked() if err != nil { - return "", fmt.Errorf("Failed to read password") + return "", xerrors.New("Failed to read a password") } if 0 < len(pass) { return string(pass), nil diff --git a/config/config.go b/config/config.go index 3a41330b..4eddc3dc 100644 --- a/config/config.go +++ b/config/config.go @@ -27,6 +27,7 @@ import ( "strings" syslog "github.com/RackSec/srslog" + "golang.org/x/xerrors" valid "github.com/asaskevich/govalidator" log "github.com/sirupsen/logrus" @@ -167,7 +168,7 @@ func (c Config) ValidateOnConfigtest() bool { errs := []error{} if runtime.GOOS == "windows" && !c.SSHNative { - errs = append(errs, fmt.Errorf("-ssh-native-insecure is needed on windows")) + errs = append(errs, xerrors.New("-ssh-native-insecure is needed on windows")) } _, err := valid.ValidateStruct(c) @@ -188,25 +189,25 @@ func (c Config) ValidateOnScan() bool { if len(c.ResultsDir) != 0 { if ok, _ := valid.IsFilePath(c.ResultsDir); !ok { - errs = append(errs, fmt.Errorf( + errs = append(errs, xerrors.Errorf( "JSON base directory must be a *Absolute* file path. -results-dir: %s", c.ResultsDir)) } } if runtime.GOOS == "windows" && !c.SSHNative { - errs = append(errs, fmt.Errorf("-ssh-native-insecure is needed on windows")) + errs = append(errs, xerrors.New("-ssh-native-insecure is needed on windows")) } if len(c.ResultsDir) != 0 { if ok, _ := valid.IsFilePath(c.ResultsDir); !ok { - errs = append(errs, fmt.Errorf( + errs = append(errs, xerrors.Errorf( "JSON base directory must be a *Absolute* file path. -results-dir: %s", c.ResultsDir)) } } if len(c.CacheDBPath) != 0 { if ok, _ := valid.IsFilePath(c.CacheDBPath); !ok { - errs = append(errs, fmt.Errorf( + errs = append(errs, xerrors.Errorf( "Cache DB path must be a *Absolute* file path. -cache-dbpath: %s", c.CacheDBPath)) } @@ -233,7 +234,7 @@ func (c Config) ValidateOnReportDB() bool { } if c.CveDict.Type == "sqlite3" { if _, err := os.Stat(c.CveDict.SQLite3Path); os.IsNotExist(err) { - errs = append(errs, fmt.Errorf("SQLite3 DB path (%s) is not exist: %s", "cvedb", c.CveDict.SQLite3Path)) + errs = append(errs, xerrors.Errorf("SQLite3 DB path (%s) is not exist: %s", "cvedb", c.CveDict.SQLite3Path)) } } @@ -262,7 +263,7 @@ func (c Config) ValidateOnReport() bool { if len(c.ResultsDir) != 0 { if ok, _ := valid.IsFilePath(c.ResultsDir); !ok { - errs = append(errs, fmt.Errorf( + errs = append(errs, xerrors.Errorf( "JSON base directory must be a *Absolute* file path. -results-dir: %s", c.ResultsDir)) } } @@ -321,7 +322,7 @@ func (c Config) ValidateOnTui() bool { if len(c.ResultsDir) != 0 { if ok, _ := valid.IsFilePath(c.ResultsDir); !ok { - errs = append(errs, fmt.Errorf( + errs = append(errs, xerrors.Errorf( "JSON base directory must be a *Absolute* file path. -results-dir: %s", c.ResultsDir)) } } @@ -331,7 +332,7 @@ func (c Config) ValidateOnTui() bool { } if c.CveDict.Type == "sqlite3" { if _, err := os.Stat(c.CveDict.SQLite3Path); os.IsNotExist(err) { - errs = append(errs, fmt.Errorf("SQLite3 DB path (%s) is not exist: %s", "cvedb", c.CveDict.SQLite3Path)) + errs = append(errs, xerrors.Errorf("SQLite3 DB path (%s) is not exist: %s", "cvedb", c.CveDict.SQLite3Path)) } } @@ -351,35 +352,35 @@ func validateDB(dictionaryDBName, dbType, dbPath, dbURL string) error { switch dbType { case "sqlite3": if dbURL != "" { - return fmt.Errorf("To use SQLite3, specify -%s-type=sqlite3 and -%s-path. To use as http server mode, specify -%s-type=http and -%s-url", + return xerrors.Errorf("To use SQLite3, specify -%s-type=sqlite3 and -%s-path. To use as http server mode, specify -%s-type=http and -%s-url", dictionaryDBName, dictionaryDBName, dictionaryDBName, dictionaryDBName) } if ok, _ := valid.IsFilePath(dbPath); !ok { - return fmt.Errorf("SQLite3 path must be a *Absolute* file path. -%s-path: %s", + return xerrors.Errorf("SQLite3 path must be a *Absolute* file path. -%s-path: %s", dictionaryDBName, dbPath) } case "mysql": if dbURL == "" { - return fmt.Errorf(`MySQL connection string is needed. -%s-url="user:pass@tcp(localhost:3306)/dbname"`, + return xerrors.Errorf(`MySQL connection string is needed. -%s-url="user:pass@tcp(localhost:3306)/dbname"`, dictionaryDBName) } case "postgres": if dbURL == "" { - return fmt.Errorf(`PostgreSQL connection string is needed. -%s-url="host=myhost user=user dbname=dbname sslmode=disable password=password"`, + return xerrors.Errorf(`PostgreSQL connection string is needed. -%s-url="host=myhost user=user dbname=dbname sslmode=disable password=password"`, dictionaryDBName) } case "redis": if dbURL == "" { - return fmt.Errorf(`Redis connection string is needed. -%s-url="redis://localhost/0"`, + return xerrors.Errorf(`Redis connection string is needed. -%s-url="redis://localhost/0"`, dictionaryDBName) } case "http": if dbURL == "" { - return fmt.Errorf(`URL is needed. -%s-url="http://localhost:1323"`, + return xerrors.Errorf(`URL is needed. -%s-url="http://localhost:1323"`, dictionaryDBName) } default: - return fmt.Errorf("%s type must be either 'sqlite3', 'mysql', 'postgres', 'redis' or 'http'. -%s-type: %s", + return xerrors.Errorf("%s type must be either 'sqlite3', 'mysql', 'postgres', 'redis' or 'http'. -%s-type: %s", dictionaryDBName, dictionaryDBName, dbType) } return nil @@ -403,7 +404,7 @@ func checkEmails(emails []string) (errs []error) { return } if ok := valid.IsEmail(addr); !ok { - errs = append(errs, fmt.Errorf("Invalid email address. email: %s", addr)) + errs = append(errs, xerrors.Errorf("Invalid email address. email: %s", addr)) } } return @@ -425,16 +426,16 @@ func (c *SMTPConf) Validate() (errs []error) { } if len(c.SMTPAddr) == 0 { - errs = append(errs, fmt.Errorf("email.smtpAddr must not be empty")) + errs = append(errs, xerrors.New("email.smtpAddr must not be empty")) } if len(c.SMTPPort) == 0 { - errs = append(errs, fmt.Errorf("email.smtpPort must not be empty")) + errs = append(errs, xerrors.New("email.smtpPort must not be empty")) } if len(c.To) == 0 { - errs = append(errs, fmt.Errorf("email.To required at least one address")) + errs = append(errs, xerrors.New("email.To required at least one address")) } if len(c.From) == 0 { - errs = append(errs, fmt.Errorf("email.From required at least one address")) + errs = append(errs, xerrors.New("email.From required at least one address")) } _, err := valid.ValidateStruct(c) @@ -457,11 +458,11 @@ func (c *StrideConf) Validate() (errs []error) { } if len(c.HookURL) == 0 { - errs = append(errs, fmt.Errorf("stride.HookURL must not be empty")) + errs = append(errs, xerrors.New("stride.HookURL must not be empty")) } if len(c.AuthToken) == 0 { - errs = append(errs, fmt.Errorf("stride.AuthToken must not be empty")) + errs = append(errs, xerrors.New("stride.AuthToken must not be empty")) } _, err := valid.ValidateStruct(c) @@ -489,21 +490,21 @@ func (c *SlackConf) Validate() (errs []error) { } if len(c.HookURL) == 0 && len(c.LegacyToken) == 0 { - errs = append(errs, fmt.Errorf("slack.hookURL or slack.LegacyToken must not be empty")) + errs = append(errs, xerrors.New("slack.hookURL or slack.LegacyToken must not be empty")) } if len(c.Channel) == 0 { - errs = append(errs, fmt.Errorf("slack.channel must not be empty")) + errs = append(errs, xerrors.New("slack.channel must not be empty")) } else { if !(strings.HasPrefix(c.Channel, "#") || c.Channel == "${servername}") { - errs = append(errs, fmt.Errorf( + errs = append(errs, xerrors.Errorf( "channel's prefix must be '#', channel: %s", c.Channel)) } } if len(c.AuthUser) == 0 { - errs = append(errs, fmt.Errorf("slack.authUser must not be empty")) + errs = append(errs, xerrors.New("slack.authUser must not be empty")) } _, err := valid.ValidateStruct(c) @@ -526,11 +527,11 @@ func (c *HipChatConf) Validate() (errs []error) { return } if len(c.Room) == 0 { - errs = append(errs, fmt.Errorf("hipcaht.room must not be empty")) + errs = append(errs, xerrors.New("hipcaht.room must not be empty")) } if len(c.AuthToken) == 0 { - errs = append(errs, fmt.Errorf("hipcaht.AuthToken must not be empty")) + errs = append(errs, xerrors.New("hipcaht.AuthToken must not be empty")) } _, err := valid.ValidateStruct(c) @@ -552,11 +553,11 @@ func (c *ChatWorkConf) Validate() (errs []error) { return } if len(c.Room) == 0 { - errs = append(errs, fmt.Errorf("chatworkcaht.room must not be empty")) + errs = append(errs, xerrors.New("chatworkcaht.room must not be empty")) } if len(c.APIToken) == 0 { - errs = append(errs, fmt.Errorf("chatworkcaht.ApiToken must not be empty")) + errs = append(errs, xerrors.New("chatworkcaht.ApiToken must not be empty")) } _, err := valid.ValidateStruct(c) @@ -578,11 +579,11 @@ func (c *TelegramConf) Validate() (errs []error) { return } if len(c.ChatID) == 0 { - errs = append(errs, fmt.Errorf("TelegramConf.ChatID must not be empty")) + errs = append(errs, xerrors.New("TelegramConf.ChatID must not be empty")) } if len(c.Token) == 0 { - errs = append(errs, fmt.Errorf("TelegramConf.Token must not be empty")) + errs = append(errs, xerrors.New("TelegramConf.Token must not be empty")) } _, err := valid.ValidateStruct(c) @@ -606,15 +607,15 @@ func (c *SaasConf) Validate() (errs []error) { } if c.GroupID == 0 { - errs = append(errs, fmt.Errorf("saas.GroupID must not be empty")) + errs = append(errs, xerrors.New("saas.GroupID must not be empty")) } if len(c.Token) == 0 { - errs = append(errs, fmt.Errorf("saas.Token must not be empty")) + errs = append(errs, xerrors.New("saas.Token must not be empty")) } if len(c.URL) == 0 { - errs = append(errs, fmt.Errorf("saas.URL must not be empty")) + errs = append(errs, xerrors.New("saas.URL must not be empty")) } _, err := valid.ValidateStruct(c) @@ -688,7 +689,7 @@ func (c *SyslogConf) GetSeverity() (syslog.Priority, error) { case "debug": return syslog.LOG_DEBUG, nil default: - return -1, fmt.Errorf("Invalid severity: %s", c.Severity) + return -1, xerrors.Errorf("Invalid severity: %s", c.Severity) } } @@ -740,7 +741,7 @@ func (c *SyslogConf) GetFacility() (syslog.Priority, error) { case "local7": return syslog.LOG_LOCAL7, nil default: - return -1, fmt.Errorf("Invalid facility: %s", c.Facility) + return -1, xerrors.Errorf("Invalid facility: %s", c.Facility) } } @@ -1041,16 +1042,16 @@ type Azure struct { // ServerInfo has SSH Info, additional CPE packages to scan. type ServerInfo struct { - ServerName string `toml:"-" json:"serverName"` - User string `toml:"user,omitempty" json:"user"` - Host string `toml:"host,omitempty" json:"host"` - Port string `toml:"port,omitempty" json:"port"` - KeyPath string `toml:"keyPath,omitempty" json:"keyPath"` - KeyPassword string `json:"-" toml:"-"` + ServerName string `toml:"-" json:"serverName,omitempty"` + User string `toml:"user,omitempty" json:"user,omitempty"` + Host string `toml:"host,omitempty" json:"host,omitempty"` + Port string `toml:"port,omitempty" json:"port,omitempty"` + KeyPath string `toml:"keyPath,omitempty" json:"keyPath,omitempty"` + KeyPassword string `json:"-,omitempty" toml:"-"` CpeNames []string `toml:"cpeNames,omitempty" json:"cpeNames,omitempty"` ScanMode []string `toml:"scanMode,omitempty" json:"scanMode,omitempty"` DependencyCheckXMLPath string `toml:"dependencyCheckXMLPath,omitempty" json:"-"` // TODO Deprecated remove in near future - OwaspDCXMLPath string `toml:"owaspDCXMLPath,omitempty" json:"owaspDCXMLPath"` + OwaspDCXMLPath string `toml:"owaspDCXMLPath,omitempty" json:"owaspDCXMLPath,omitempty"` ContainersIncluded []string `toml:"containersIncluded,omitempty" json:"containersIncluded,omitempty"` ContainersExcluded []string `toml:"containersExcluded,omitempty" json:"containersExcluded,omitempty"` ContainerType string `toml:"containerType,omitempty" json:"containerType,omitempty"` @@ -1059,14 +1060,18 @@ type ServerInfo struct { IgnorePkgsRegexp []string `toml:"ignorePkgsRegexp,omitempty" json:"ignorePkgsRegexp,omitempty"` GitHubRepos map[string]GitHubConf `toml:"githubs" json:"githubs,omitempty"` // key: owner/repo UUIDs map[string]string `toml:"uuids,omitempty" json:"uuids,omitempty"` - Memo string `toml:"memo,omitempty" json:"memo"` + Memo string `toml:"memo,omitempty" json:"memo,omitempty"` Enablerepo []string `toml:"enablerepo,omitempty" json:"enablerepo,omitempty"` // For CentOS, RHEL, Amazon Optional map[string]interface{} `toml:"optional,omitempty" json:"optional,omitempty"` // Optional key-value set that will be outputted to JSON - Type string `toml:"type,omitempty" json:"type"` // "pseudo" or "" - IPv4Addrs []string `toml:"-" json:"ipv4Addrs,omitempty"` - IPv6Addrs []string `toml:"-" json:"ipv6Addrs,omitempty"` + + Type string `toml:"type,omitempty" json:"type,omitempty"` // "pseudo" or "" + + WordPress WordPressConf `toml:"wordpress,omitempty" json:"wordpress,omitempty"` // used internal + IPv4Addrs []string `toml:"-" json:"ipv4Addrs,omitempty"` + IPv6Addrs []string `toml:"-" json:"ipv6Addrs,omitempty"` + LogMsgAnsiColor string `toml:"-" json:"-"` // DebugLog Color Container Container `toml:"-" json:"-"` Distro Distro `toml:"-" json:"-"` @@ -1081,21 +1086,18 @@ type ContainerSetting struct { IgnoreCves []string `json:"ignoreCves,omitempty"` } -// IntegrationConf is used for integration configuration -type IntegrationConf struct { - GitHubConf map[string]GitHubConf -} - -// New creates IntegrationConf and initialize fields -func (c IntegrationConf) New() IntegrationConf { - return IntegrationConf{ - GitHubConf: map[string]GitHubConf{}, - } +// WordPressConf used for WordPress Scanning +type WordPressConf struct { + OSUser string `toml:"osUser" json:"osUser,omitempty"` + DocRoot string `toml:"docRoot" json:"docRoot,omitempty"` + CmdPath string `toml:"cmdPath" json:"cmdPath,omitempty"` + WPVulnDBToken string `toml:"wpVulnDBToken" json:"-,omitempty"` + IgnoreInactive bool `json:"ignoreInactive,omitempty"` } // GitHubConf is used for GitHub integration type GitHubConf struct { - Token string `json:"token"` + Token string `json:"-"` } // ScanMode has a type of scan mode. fast, fast-root, deep and offline @@ -1138,9 +1140,9 @@ func (s ScanMode) validate() error { if numTrue == 0 { s.Set(Fast) } else if s.IsDeep() && s.IsOffline() { - return fmt.Errorf("Don't specify both of -deep and offline") + return xerrors.New("Don't specify both of -deep and offline") } else if numTrue != 1 { - return fmt.Errorf("Specify only one of -fast, -fast-root or -deep") + return xerrors.New("Specify only one of -fast, -fast-root or -deep") } return nil } @@ -1203,7 +1205,7 @@ func (l Distro) MajorVersion() (ver int, err error) { if 0 < len(l.Release) { ver, err = strconv.Atoi(strings.Split(l.Release, ".")[0]) } else { - err = fmt.Errorf("Release is empty") + err = xerrors.New("Release is empty") } return } diff --git a/config/jsonloader.go b/config/jsonloader.go index b8fdb951..da685b5c 100644 --- a/config/jsonloader.go +++ b/config/jsonloader.go @@ -17,7 +17,7 @@ along with this program. If not, see . package config -import "fmt" +import "golang.org/x/xerrors" // JSONLoader loads configuration type JSONLoader struct { @@ -25,5 +25,5 @@ type JSONLoader struct { // Load load the configuration JSON file specified by path arg. func (c JSONLoader) Load(path, sudoPass, keyPass string) (err error) { - return fmt.Errorf("Not implement yet") + return xerrors.New("Not implement yet") } diff --git a/config/tomlloader.go b/config/tomlloader.go index 937fd675..aa72406e 100644 --- a/config/tomlloader.go +++ b/config/tomlloader.go @@ -18,13 +18,13 @@ along with this program. If not, see . package config import ( - "fmt" "os" "regexp" "strings" "github.com/BurntSushi/toml" "github.com/knqyf263/go-cpe/naming" + "golang.org/x/xerrors" ) // TOMLLoader loads config @@ -65,14 +65,14 @@ func (c TOMLLoader) Load(pathToToml, keyPass string) error { i := 0 for serverName, v := range conf.Servers { if 0 < len(v.KeyPassword) { - return fmt.Errorf("[Deprecated] KEYPASSWORD IN CONFIG FILE ARE UNSECURE. REMOVE THEM IMMEDIATELY FOR A SECURITY REASONS. THEY WILL BE REMOVED IN A FUTURE RELEASE: %s", serverName) + 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) } s := ServerInfo{ServerName: serverName} if v.Type != ServerTypePseudo { s.Host = v.Host if len(s.Host) == 0 { - return fmt.Errorf("%s is invalid. host is empty", serverName) + return xerrors.Errorf("%s is invalid. host is empty", serverName) } switch { @@ -91,7 +91,7 @@ func (c TOMLLoader) Load(pathToToml, keyPass string) error { s.User = d.User default: if s.Port != "local" { - return fmt.Errorf("%s is invalid. User is empty", serverName) + return xerrors.Errorf("%s is invalid. User is empty", serverName) } } @@ -101,7 +101,7 @@ func (c TOMLLoader) Load(pathToToml, keyPass string) error { } if s.KeyPath != "" { if _, err := os.Stat(s.KeyPath); err != nil { - return fmt.Errorf( + return xerrors.Errorf( "%s is invalid. keypath: %s not exists", serverName, s.KeyPath) } } @@ -130,11 +130,11 @@ func (c TOMLLoader) Load(pathToToml, keyPass string) error { case "offline": s.Mode.Set(Offline) default: - return fmt.Errorf("scanMode: %s of %s is invalie. Specify -fast, -fast-root, -deep or offline", m, serverName) + return xerrors.Errorf("scanMode: %s of %s is invalie. Specify -fast, -fast-root, -deep or offline", m, serverName) } } if err := s.Mode.validate(); err != nil { - return fmt.Errorf("%s in %s", err, serverName) + return xerrors.Errorf("%s in %s", err, serverName) } s.CpeNames = v.CpeNames @@ -145,7 +145,7 @@ func (c TOMLLoader) Load(pathToToml, keyPass string) error { for i, n := range s.CpeNames { uri, err := toCpeURI(n) if err != nil { - return fmt.Errorf("Failed to parse CPENames %s in %s: %s", n, serverName, err) + return xerrors.Errorf("Failed to parse CPENames %s in %s, err: %w", n, serverName, err) } s.CpeNames[i] = uri } @@ -172,7 +172,7 @@ func (c TOMLLoader) Load(pathToToml, keyPass string) error { } if len(v.DependencyCheckXMLPath) != 0 || len(d.DependencyCheckXMLPath) != 0 { - return fmt.Errorf("[DEPRECATED] dependencyCheckXMLPath IS DEPRECATED. USE owaspDCXMLPath INSTEAD: %s", serverName) + return xerrors.Errorf("[DEPRECATED] dependencyCheckXMLPath IS DEPRECATED. USE owaspDCXMLPath INSTEAD: %s", serverName) } s.OwaspDCXMLPath = v.OwaspDCXMLPath @@ -215,14 +215,14 @@ func (c TOMLLoader) Load(pathToToml, keyPass string) error { for _, reg := range s.IgnorePkgsRegexp { _, err := regexp.Compile(reg) if err != nil { - return fmt.Errorf("Faild to parse %s in %s. err: %s", reg, serverName, err) + return xerrors.Errorf("Faild to parse %s in %s. err: %w", reg, serverName, err) } } for contName, cont := range s.Containers { for _, reg := range cont.IgnorePkgsRegexp { _, err := regexp.Compile(reg) if err != nil { - return fmt.Errorf("Faild to parse %s in %s@%s. err: %s", + return xerrors.Errorf("Faild to parse %s in %s@%s. err: %w", reg, contName, serverName, err) } } @@ -247,7 +247,7 @@ func (c TOMLLoader) Load(pathToToml, keyPass string) error { case "base", "updates": // nop default: - return fmt.Errorf( + return xerrors.Errorf( "For now, enablerepo have to be base or updates: %s, servername: %s", s.Enablerepo, serverName) } @@ -257,11 +257,11 @@ func (c TOMLLoader) Load(pathToToml, keyPass string) error { s.GitHubRepos = v.GitHubRepos for ownerRepo, githubSetting := range s.GitHubRepos { if ss := strings.Split(ownerRepo, "/"); len(ss) != 2 { - return fmt.Errorf("Failed to parse GitHub owner/repo: %s in %s", + return xerrors.Errorf("Failed to parse GitHub owner/repo: %s in %s", ownerRepo, serverName) } if githubSetting.Token == "" { - return fmt.Errorf("GitHub owner/repo: %s in %s token is empty", + return xerrors.Errorf("GitHub owner/repo: %s in %s token is empty", ownerRepo, serverName) } } @@ -269,6 +269,12 @@ func (c TOMLLoader) Load(pathToToml, keyPass string) error { 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.LogMsgAnsiColor = Colors[i%len(Colors)] i++ @@ -292,5 +298,5 @@ func toCpeURI(cpename string) (string, error) { } return naming.BindToURI(wfn), nil } - return "", fmt.Errorf("Unknow CPE format: %s", cpename) + return "", xerrors.Errorf("Unknow CPE format: %s", cpename) } diff --git a/contrib/owasp-dependency-check/parser/parser.go b/contrib/owasp-dependency-check/parser/parser.go index a729723b..0e383446 100644 --- a/contrib/owasp-dependency-check/parser/parser.go +++ b/contrib/owasp-dependency-check/parser/parser.go @@ -2,12 +2,12 @@ package parser import ( "encoding/xml" - "fmt" "io/ioutil" "os" "strings" log "github.com/sirupsen/logrus" + "golang.org/x/xerrors" ) type analysis struct { @@ -49,7 +49,7 @@ func Parse(path string) ([]string, error) { var anal analysis if err := xml.Unmarshal(b, &anal); err != nil { - return nil, fmt.Errorf("Failed to unmarshal: %s", err) + return nil, xerrors.Errorf("Failed to unmarshal: %s", err) } cpes := []string{} diff --git a/exploit/exploit.go b/exploit/exploit.go index 33d422b0..3afb450a 100644 --- a/exploit/exploit.go +++ b/exploit/exploit.go @@ -28,6 +28,7 @@ import ( "github.com/mozqnet/go-exploitdb/db" exploitmodels "github.com/mozqnet/go-exploitdb/models" "github.com/parnurzeal/gorequest" + "golang.org/x/xerrors" ) // FillWithExploit fills exploit information that has in Exploit @@ -112,8 +113,7 @@ func CheckHTTPHealth() error { // resp, _, errs = gorequest.New().SetDebug(config.Conf.Debug).Get(url).End() // resp, _, errs = gorequest.New().Proxy(api.httpProxy).Get(url).End() if 0 < len(errs) || resp == nil || resp.StatusCode != 200 { - return fmt.Errorf("Failed to connect to exploit server. url: %s, errs: %v", - url, errs) + return xerrors.Errorf("Failed to connect to exploit server. url: %s, errs: %w", url, errs) } return nil } diff --git a/exploit/util.go b/exploit/util.go index 904cfd18..ebdaa9e7 100644 --- a/exploit/util.go +++ b/exploit/util.go @@ -18,13 +18,13 @@ along with this program. If not, see . package exploit import ( - "fmt" "net/http" "time" "github.com/cenkalti/backoff" "github.com/future-architect/vuls/util" "github.com/parnurzeal/gorequest" + "golang.org/x/xerrors" ) type response struct { @@ -79,11 +79,11 @@ func getCvesViaHTTP(cveIDs []string, urlPrefix string) ( case err := <-errChan: errs = append(errs, err) case <-timeout: - return nil, fmt.Errorf("Timeout Fetching OVAL") + return nil, xerrors.New("Timeout Fetching OVAL") } } if len(errs) != 0 { - return nil, fmt.Errorf("Failed to fetch OVAL. err: %v", errs) + return nil, xerrors.Errorf("Failed to fetch OVAL. err: %w", errs) } return } @@ -108,8 +108,7 @@ func httpGet(url string, req request, resChan chan<- response, errChan chan<- er if count == retryMax { return nil } - return fmt.Errorf("HTTP GET error: %v, url: %s, resp: %v", - errs, url, resp) + return xerrors.Errorf("HTTP GET error, url: %s, resp: %v, err: %w", url, resp, errs) } return nil } @@ -118,11 +117,11 @@ func httpGet(url string, req request, resChan chan<- response, errChan chan<- er } err := backoff.RetryNotify(f, backoff.NewExponentialBackOff(), notify) if err != nil { - errChan <- fmt.Errorf("HTTP Error %s", err) + errChan <- xerrors.Errorf("HTTP Error %w", err) return } if count == retryMax { - errChan <- fmt.Errorf("HRetry count exceeded") + errChan <- xerrors.New("Retry count exceeded") return } diff --git a/gost/debian.go b/gost/debian.go index 7d608104..0fc6a7c1 100644 --- a/gost/debian.go +++ b/gost/debian.go @@ -147,7 +147,7 @@ func (deb Debian) FillWithGost(driver db.DB, r *models.ScanResult) (nCVEs int, e } for _, name := range names { - v.AffectedPackages = v.AffectedPackages.Store(models.PackageStatus{ + v.AffectedPackages = v.AffectedPackages.Store(models.PackageFixStatus{ Name: name, FixState: "open", NotFixedYet: true, diff --git a/gost/gost.go b/gost/gost.go index 56be27d9..fb437fd9 100644 --- a/gost/gost.go +++ b/gost/gost.go @@ -26,6 +26,7 @@ import ( "github.com/future-architect/vuls/models" "github.com/knqyf263/gost/db" "github.com/parnurzeal/gorequest" + "golang.org/x/xerrors" ) // Client is the interface of OVAL client. @@ -71,8 +72,7 @@ func (b Base) CheckHTTPHealth() error { // resp, _, errs = gorequest.New().SetDebug(config.Conf.Debug).Get(url).End() // resp, _, errs = gorequest.New().Proxy(api.httpProxy).Get(url).End() if 0 < len(errs) || resp == nil || resp.StatusCode != 200 { - return fmt.Errorf("Failed to connect to gost server. url: %s, errs: %v", - url, errs) + return xerrors.Errorf("Failed to connect to gost server. url: %s, errs: %w", url, errs) } return nil } diff --git a/gost/gost_test.go b/gost/gost_test.go index f748e716..81608a84 100644 --- a/gost/gost_test.go +++ b/gost/gost_test.go @@ -14,7 +14,7 @@ func TestSetPackageStates(t *testing.T) { installed models.Packages release string in models.VulnInfo - out models.PackageStatuses + out models.PackageFixStatuses }{ //0 one @@ -31,7 +31,7 @@ func TestSetPackageStates(t *testing.T) { }, release: "7", in: models.VulnInfo{}, - out: []models.PackageStatus{ + out: []models.PackageFixStatus{ { Name: "bouncycastle", FixState: "Will not fix", @@ -66,7 +66,7 @@ func TestSetPackageStates(t *testing.T) { }, release: "7", in: models.VulnInfo{}, - out: []models.PackageStatus{ + out: []models.PackageFixStatus{ { Name: "bouncycastle", FixState: "Will not fix", @@ -94,9 +94,9 @@ func TestSetPackageStates(t *testing.T) { }, release: "7", in: models.VulnInfo{ - AffectedPackages: models.PackageStatuses{}, + AffectedPackages: models.PackageFixStatuses{}, }, - out: models.PackageStatuses{}, + out: models.PackageFixStatuses{}, }, //3 look only the same os release. @@ -113,9 +113,9 @@ func TestSetPackageStates(t *testing.T) { }, release: "7", in: models.VulnInfo{ - AffectedPackages: models.PackageStatuses{}, + AffectedPackages: models.PackageFixStatuses{}, }, - out: models.PackageStatuses{}, + out: models.PackageFixStatuses{}, }, } diff --git a/gost/redhat.go b/gost/redhat.go index 26b0e4b1..5c4a8896 100644 --- a/gost/redhat.go +++ b/gost/redhat.go @@ -191,7 +191,7 @@ func (red RedHat) fillUnfixed(driver db.DB, r *models.ScanResult) (nCVEs int, er return nCVEs, nil } -func (red RedHat) mergePackageStates(v models.VulnInfo, ps []gostmodels.RedhatPackageState, installed models.Packages, release string) (pkgStats models.PackageStatuses) { +func (red RedHat) mergePackageStates(v models.VulnInfo, ps []gostmodels.RedhatPackageState, installed models.Packages, release string) (pkgStats models.PackageFixStatuses) { pkgStats = v.AffectedPackages for _, pstate := range ps { if pstate.Cpe != @@ -214,7 +214,7 @@ func (red RedHat) mergePackageStates(v models.VulnInfo, ps []gostmodels.RedhatPa notFixedYet = true } - pkgStats = pkgStats.Store(models.PackageStatus{ + pkgStats = pkgStats.Store(models.PackageFixStatus{ Name: pstate.PackageName, FixState: pstate.FixState, NotFixedYet: notFixedYet, diff --git a/gost/util.go b/gost/util.go index b5ae77aa..840098ef 100644 --- a/gost/util.go +++ b/gost/util.go @@ -18,7 +18,6 @@ along with this program. If not, see . package gost import ( - "fmt" "net/http" "time" @@ -26,6 +25,7 @@ import ( "github.com/future-architect/vuls/models" "github.com/future-architect/vuls/util" "github.com/parnurzeal/gorequest" + "golang.org/x/xerrors" ) type response struct { @@ -80,11 +80,11 @@ func getCvesViaHTTP(cveIDs []string, urlPrefix string) ( case err := <-errChan: errs = append(errs, err) case <-timeout: - return nil, fmt.Errorf("Timeout Fetching OVAL") + return nil, xerrors.New("Timeout Fetching OVAL") } } if len(errs) != 0 { - return nil, fmt.Errorf("Failed to fetch OVAL. err: %v", errs) + return nil, xerrors.Errorf("Failed to fetch OVAL. err: %w", errs) } return } @@ -154,11 +154,11 @@ func getAllUnfixedCvesViaHTTP(r *models.ScanResult, urlPrefix string) ( case err := <-errChan: errs = append(errs, err) case <-timeout: - return nil, fmt.Errorf("Timeout Fetching OVAL") + return nil, xerrors.New("Timeout Fetching OVAL") } } if len(errs) != 0 { - return nil, fmt.Errorf("Failed to fetch OVAL. err: %v", errs) + return nil, xerrors.Errorf("Failed to fetch OVAL. err: %w", errs) } return } @@ -176,8 +176,7 @@ func httpGet(url string, req request, resChan chan<- response, errChan chan<- er if count == retryMax { return nil } - return fmt.Errorf("HTTP GET error: %v, url: %s, resp: %v", - errs, url, resp) + return xerrors.Errorf("HTTP GET error, url: %s, resp: %v, err: %w", url, resp, errs) } return nil } @@ -186,11 +185,11 @@ func httpGet(url string, req request, resChan chan<- response, errChan chan<- er } err := backoff.RetryNotify(f, backoff.NewExponentialBackOff(), notify) if err != nil { - errChan <- fmt.Errorf("HTTP Error %s", err) + errChan <- xerrors.Errorf("HTTP Error %w", err) return } if count == retryMax { - errChan <- fmt.Errorf("HRetry count exceeded") + errChan <- xerrors.New("Retry count exceeded") return } diff --git a/models/cvecontents.go b/models/cvecontents.go index 09005027..45ab421e 100644 --- a/models/cvecontents.go +++ b/models/cvecontents.go @@ -140,6 +140,7 @@ func (v CveContents) References(myFamily string) (values []CveContentRefs) { }) } } + return } @@ -230,6 +231,8 @@ func NewCveContentType(name string) CveContentType { return DebianSecurityTracker case "microsoft": return Microsoft + case "wordpress": + return WPVulnDB default: return Unknown } @@ -269,6 +272,9 @@ const ( // Microsoft is Microsoft Microsoft CveContentType = "microsoft" + // WPVulnDB is WordPress + WPVulnDB CveContentType = "wpvulndb" + // Unknown is Unknown Unknown CveContentType = "unknown" ) @@ -286,6 +292,7 @@ var AllCveContetTypes = CveContentTypes{ Ubuntu, RedHatAPI, DebianSecurityTracker, + WPVulnDB, } // Except returns CveContentTypes except for given args diff --git a/models/packages.go b/models/packages.go index 54238396..23b99198 100644 --- a/models/packages.go +++ b/models/packages.go @@ -21,6 +21,8 @@ import ( "bytes" "fmt" "strings" + + "golang.org/x/xerrors" ) // Packages is Map of Package @@ -83,7 +85,7 @@ func (ps Packages) FindByFQPN(nameVerRelArc string) (*Package, error) { return &p, nil } } - return nil, fmt.Errorf("Failed to find the package: %s", nameVerRelArc) + return nil, xerrors.Errorf("Failed to find the package: %s", nameVerRelArc) } // Package has installed binary packages. diff --git a/models/scanresults.go b/models/scanresults.go index caa27713..f6366d11 100644 --- a/models/scanresults.go +++ b/models/scanresults.go @@ -36,35 +36,37 @@ type ScanResults []ScanResult // ScanResult has the result of scanned CVE information. type ScanResult struct { - JSONVersion int `json:"jsonVersion"` - Lang string `json:"lang"` - ServerUUID string `json:"serverUUID"` - ServerName string `json:"serverName"` // TOML Section key - Family string `json:"family"` - Release string `json:"release"` - Container Container `json:"container"` - Platform Platform `json:"platform"` - IPv4Addrs []string `json:"ipv4Addrs,omitempty"` // only global unicast address (https://golang.org/pkg/net/#IP.IsGlobalUnicast) - IPv6Addrs []string `json:"ipv6Addrs,omitempty"` // only global unicast address (https://golang.org/pkg/net/#IP.IsGlobalUnicast) - ScannedAt time.Time `json:"scannedAt"` - ScanMode string `json:"scanMode"` - ScannedVersion string `json:"scannedVersion"` - ScannedRevision string `json:"scannedRevision"` - ScannedBy string `json:"scannedBy"` - ScannedIPv4Addrs []string `json:"scannedIpv4Addrs"` - ScannedIPv6Addrs []string `json:"scannedIpv6Addrs"` - ReportedAt time.Time `json:"reportedAt"` - ReportedVersion string `json:"reportedVersion"` - ReportedRevision string `json:"reportedRevision"` - ReportedBy string `json:"reportedBy"` - ScannedCves VulnInfos `json:"scannedCves"` - RunningKernel Kernel `json:"runningKernel"` - Packages Packages `json:"packages"` - CweDict CweDict `json:"cweDict"` - Optional map[string]interface{} `json:",omitempty"` - SrcPackages SrcPackages `json:",omitempty"` - Errors []string `json:"errors"` - Config struct { + JSONVersion int `json:"jsonVersion"` + Lang string `json:"lang"` + ServerUUID string `json:"serverUUID"` + ServerName string `json:"serverName"` // TOML Section key + Family string `json:"family"` + Release string `json:"release"` + Container Container `json:"container"` + Platform Platform `json:"platform"` + IPv4Addrs []string `json:"ipv4Addrs,omitempty"` // only global unicast address (https://golang.org/pkg/net/#IP.IsGlobalUnicast) + IPv6Addrs []string `json:"ipv6Addrs,omitempty"` // only global unicast address (https://golang.org/pkg/net/#IP.IsGlobalUnicast) + ScannedAt time.Time `json:"scannedAt"` + ScanMode string `json:"scanMode"` + ScannedVersion string `json:"scannedVersion"` + ScannedRevision string `json:"scannedRevision"` + ScannedBy string `json:"scannedBy"` + ScannedIPv4Addrs []string `json:"scannedIpv4Addrs,omitempty"` + ScannedIPv6Addrs []string `json:"scannedIpv6Addrs,omitempty"` + ReportedAt time.Time `json:"reportedAt"` + ReportedVersion string `json:"reportedVersion"` + ReportedRevision string `json:"reportedRevision"` + ReportedBy string `json:"reportedBy"` + Errors []string `json:"errors"` + + ScannedCves VulnInfos `json:"scannedCves"` + RunningKernel Kernel `json:"runningKernel"` + Packages Packages `json:"packages"` + SrcPackages SrcPackages `json:",omitempty"` + WordPressPackages *WordPressPackages `json:",omitempty"` + CweDict CweDict `json:"cweDict,omitempty"` + Optional map[string]interface{} `json:",omitempty"` + Config struct { Scan config.Config `json:"scan"` Report config.Config `json:"report"` } `json:"config"` @@ -252,6 +254,30 @@ func (r ScanResult) FilterIgnorePkgs() ScanResult { return r } +// FilterInactiveWordPressLibs is filter function. +func (r ScanResult) FilterInactiveWordPressLibs() ScanResult { + if !config.Conf.Servers[r.ServerName].WordPress.IgnoreInactive { + return r + } + + filtered := r.ScannedCves.Find(func(v VulnInfo) bool { + if len(v.WpPackageFixStats) == 0 { + return true + } + // Ignore if all libs in this vulnInfo inactive + for _, wp := range v.WpPackageFixStats { + if p, ok := r.WordPressPackages.Find(wp.Name); ok { + if p.Status != Inactive { + return true + } + } + } + return false + }) + r.ScannedCves = filtered + return r +} + // ReportFileName returns the filename on localhost without extention func (r ScanResult) ReportFileName() (name string) { if len(r.Container.ContainerID) == 0 { diff --git a/models/scanresults_test.go b/models/scanresults_test.go index 89821c71..7333b14d 100644 --- a/models/scanresults_test.go +++ b/models/scanresults_test.go @@ -348,7 +348,7 @@ func TestFilterUnfixed(t *testing.T) { ScannedCves: VulnInfos{ "CVE-2017-0001": { CveID: "CVE-2017-0001", - AffectedPackages: PackageStatuses{ + AffectedPackages: PackageFixStatuses{ { Name: "a", NotFixedYet: true, @@ -357,7 +357,7 @@ func TestFilterUnfixed(t *testing.T) { }, "CVE-2017-0002": { CveID: "CVE-2017-0002", - AffectedPackages: PackageStatuses{ + AffectedPackages: PackageFixStatuses{ { Name: "b", NotFixedYet: false, @@ -366,7 +366,7 @@ func TestFilterUnfixed(t *testing.T) { }, "CVE-2017-0003": { CveID: "CVE-2017-0003", - AffectedPackages: PackageStatuses{ + AffectedPackages: PackageFixStatuses{ { Name: "c", NotFixedYet: true, @@ -383,7 +383,7 @@ func TestFilterUnfixed(t *testing.T) { ScannedCves: VulnInfos{ "CVE-2017-0002": { CveID: "CVE-2017-0002", - AffectedPackages: PackageStatuses{ + AffectedPackages: PackageFixStatuses{ { Name: "b", NotFixedYet: false, @@ -392,7 +392,7 @@ func TestFilterUnfixed(t *testing.T) { }, "CVE-2017-0003": { CveID: "CVE-2017-0003", - AffectedPackages: PackageStatuses{ + AffectedPackages: PackageFixStatuses{ { Name: "c", NotFixedYet: true, @@ -435,7 +435,7 @@ func TestFilterIgnorePkgs(t *testing.T) { ScannedCves: VulnInfos{ "CVE-2017-0001": { CveID: "CVE-2017-0001", - AffectedPackages: PackageStatuses{ + AffectedPackages: PackageFixStatuses{ {Name: "kernel"}, }, }, @@ -462,7 +462,7 @@ func TestFilterIgnorePkgs(t *testing.T) { ScannedCves: VulnInfos{ "CVE-2017-0001": { CveID: "CVE-2017-0001", - AffectedPackages: PackageStatuses{ + AffectedPackages: PackageFixStatuses{ {Name: "kernel"}, {Name: "vim"}, }, @@ -475,7 +475,7 @@ func TestFilterIgnorePkgs(t *testing.T) { ScannedCves: VulnInfos{ "CVE-2017-0001": { CveID: "CVE-2017-0001", - AffectedPackages: PackageStatuses{ + AffectedPackages: PackageFixStatuses{ {Name: "kernel"}, {Name: "vim"}, }, @@ -491,7 +491,7 @@ func TestFilterIgnorePkgs(t *testing.T) { ScannedCves: VulnInfos{ "CVE-2017-0001": { CveID: "CVE-2017-0001", - AffectedPackages: PackageStatuses{ + AffectedPackages: PackageFixStatuses{ {Name: "kernel"}, {Name: "vim"}, }, @@ -545,7 +545,7 @@ func TestFilterIgnorePkgsContainer(t *testing.T) { ScannedCves: VulnInfos{ "CVE-2017-0001": { CveID: "CVE-2017-0001", - AffectedPackages: PackageStatuses{ + AffectedPackages: PackageFixStatuses{ {Name: "kernel"}, }, }, @@ -574,7 +574,7 @@ func TestFilterIgnorePkgsContainer(t *testing.T) { ScannedCves: VulnInfos{ "CVE-2017-0001": { CveID: "CVE-2017-0001", - AffectedPackages: PackageStatuses{ + AffectedPackages: PackageFixStatuses{ {Name: "kernel"}, {Name: "vim"}, }, @@ -588,7 +588,7 @@ func TestFilterIgnorePkgsContainer(t *testing.T) { ScannedCves: VulnInfos{ "CVE-2017-0001": { CveID: "CVE-2017-0001", - AffectedPackages: PackageStatuses{ + AffectedPackages: PackageFixStatuses{ {Name: "kernel"}, {Name: "vim"}, }, @@ -605,7 +605,7 @@ func TestFilterIgnorePkgsContainer(t *testing.T) { ScannedCves: VulnInfos{ "CVE-2017-0001": { CveID: "CVE-2017-0001", - AffectedPackages: PackageStatuses{ + AffectedPackages: PackageFixStatuses{ {Name: "kernel"}, {Name: "vim"}, }, diff --git a/models/vulninfos.go b/models/vulninfos.go index b0c23d23..ec787062 100644 --- a/models/vulninfos.go +++ b/models/vulninfos.go @@ -122,20 +122,19 @@ func (v VulnInfos) FormatFixedStatus(packs Packages) string { return fmt.Sprintf("%d/%d Fixed", fixed, total) } -// PackageStatuses is a list of PackageStatus -type PackageStatuses []PackageStatus +// PackageFixStatuses is a list of PackageStatus +type PackageFixStatuses []PackageFixStatus -// FormatTuiSummary format packname to show TUI summary -func (ps PackageStatuses) FormatTuiSummary() string { - names := []string{} +// Names return a slice of package names +func (ps PackageFixStatuses) Names() (names []string) { for _, p := range ps { names = append(names, p.Name) } - return strings.Join(names, ", ") + return names } // Store insert given pkg if missing, update pkg if exists -func (ps PackageStatuses) Store(pkg PackageStatus) PackageStatuses { +func (ps PackageFixStatuses) Store(pkg PackageFixStatus) PackageFixStatuses { for i, p := range ps { if p.Name == pkg.Name { ps[i] = pkg @@ -147,15 +146,15 @@ func (ps PackageStatuses) Store(pkg PackageStatus) PackageStatuses { } // Sort by Name -func (ps PackageStatuses) Sort() { +func (ps PackageFixStatuses) Sort() { sort.Slice(ps, func(i, j int) bool { return ps[i].Name < ps[j].Name }) return } -// PackageStatus has name and other status abount the package -type PackageStatus struct { +// PackageFixStatus has name and other status abount the package +type PackageFixStatus struct { Name string `json:"name"` NotFixedYet bool `json:"notFixedYet"` FixState string `json:"fixState"` @@ -163,22 +162,24 @@ type PackageStatus struct { // VulnInfo has a vulnerability information and unsecure packages type VulnInfo struct { - CveID string `json:"cveID,omitempty"` - Confidences Confidences `json:"confidences,omitempty"` - AffectedPackages PackageStatuses `json:"affectedPackages,omitempty"` - DistroAdvisories []DistroAdvisory `json:"distroAdvisories,omitempty"` // for Aamazon, RHEL, FreeBSD - CveContents CveContents `json:"cveContents,omitempty"` - Exploits []Exploit `json:"exploits,omitempty"` - AlertDict AlertDict `json:"alertDict,omitempty"` - + CveID string `json:"cveID,omitempty"` + Confidences Confidences `json:"confidences,omitempty"` + AffectedPackages PackageFixStatuses `json:"affectedPackages,omitempty"` + DistroAdvisories []DistroAdvisory `json:"distroAdvisories,omitempty"` // for Aamazon, RHEL, FreeBSD + CveContents CveContents `json:"cveContents,omitempty"` + Exploits []Exploit `json:"exploits,omitempty"` + AlertDict AlertDict `json:"alertDict,omitempty"` CpeURIs []string `json:"cpeURIs,omitempty"` // CpeURIs related to this CVE defined in config.toml GitHubSecurityAlerts GitHubSecurityAlerts `json:"gitHubSecurityAlerts,omitempty"` + WpPackageFixStats WpPackageFixStats `json:"wpPackageFixStats,omitempty"` + + VulnType string `json:"vulnType,omitempty"` } // GitHubSecurityAlerts is a list of GitHubSecurityAlert type GitHubSecurityAlerts []GitHubSecurityAlert -// Add adds given arg to the slice and return the slice (imutable) +// Add adds given arg to the slice and return the slice (immutable) func (g GitHubSecurityAlerts) Add(alert GitHubSecurityAlert) GitHubSecurityAlerts { for _, a := range g { if a.PackageName == alert.PackageName { @@ -188,12 +189,12 @@ func (g GitHubSecurityAlerts) Add(alert GitHubSecurityAlert) GitHubSecurityAlert return append(g, alert) } -func (g GitHubSecurityAlerts) String() string { - ss := []string{} +// Names return a slice of lib names +func (g GitHubSecurityAlerts) Names() (names []string) { for _, a := range g { - ss = append(ss, a.PackageName) + names = append(names, a.PackageName) } - return strings.Join(ss, ", ") + return names } // GitHubSecurityAlert has detected CVE-ID, PackageName, Status fetched via GitHub API @@ -206,6 +207,30 @@ type GitHubSecurityAlert struct { DismissReason string `json:"dismissReason"` } +// WpPackageFixStats is a list of WpPackageFixStatus +type WpPackageFixStats []WpPackageFixStatus + +// Names return a slice of names +func (ws WpPackageFixStats) Names() (names []string) { + for _, w := range ws { + names = append(names, w.Name) + } + return names +} + +// WpPackages has a list of WpPackage +type WpPackages []WpPackage + +// Add adds given arg to the slice and return the slice (immutable) +func (g WpPackages) Add(pkg WpPackage) WpPackages { + for _, a := range g { + if a.Name == pkg.Name { + return g + } + } + return append(g, pkg) +} + // Titles returns tilte (TUI) func (v VulnInfo) Titles(lang, myFamily string) (values []CveContentStr) { if lang == "ja" { @@ -278,6 +303,13 @@ func (v VulnInfo) Summaries(lang, myFamily string) (values []CveContentStr) { }) } + if v, ok := v.CveContents[WPVulnDB]; ok { + values = append(values, CveContentStr{ + Type: "WPVDB", + Value: v.Title, + }) + } + if len(values) == 0 { return []CveContentStr{{ Type: Unknown, @@ -511,15 +543,15 @@ func (v VulnInfo) AttackVector() string { for _, cnt := range v.CveContents { if strings.HasPrefix(cnt.Cvss2Vector, "AV:N") || strings.HasPrefix(cnt.Cvss3Vector, "CVSS:3.0/AV:N") { - return "Network" + return "N" } else if strings.HasPrefix(cnt.Cvss2Vector, "AV:A") || strings.HasPrefix(cnt.Cvss3Vector, "CVSS:3.0/AV:A") { - return "Adjacent" + return "A" } else if strings.HasPrefix(cnt.Cvss2Vector, "AV:L") || strings.HasPrefix(cnt.Cvss3Vector, "CVSS:3.0/AV:L") { - return "Local" + return "L" } else if strings.HasPrefix(cnt.Cvss3Vector, "CVSS:3.0/AV:P") { - return "Physical" + return "P" } } if cont, found := v.CveContents[DebianSecurityTracker]; found { @@ -538,17 +570,17 @@ func (v VulnInfo) PatchStatus(packs Packages) string { } for _, p := range v.AffectedPackages { if p.NotFixedYet { - return "Unfixed" + return "unfixed" } // fast, offline mode doesn't have new version if pack, ok := packs[p.Name]; ok { if pack.NewVersion == "" { - return "Unknown" + return "unknown" } } } - return "Fixed" + return "fixed" } // CveContentCvss has CVSS information @@ -648,6 +680,12 @@ func (v VulnInfo) Cvss3CalcURL() string { // 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 @@ -810,6 +848,9 @@ const ( // GitHubMatchStr is a String representation of GitHubMatch GitHubMatchStr = "GitHubMatch" + // WPVulnDBMatchStr is a String representation of WordPress VulnDB scanning + WPVulnDBMatchStr = "WPVulnDBMatch" + // FailedToGetChangelog is a String representation of FailedToGetChangelog FailedToGetChangelog = "FailedToGetChangelog" @@ -844,4 +885,7 @@ var ( // GitHubMatch is a ranking how confident the CVE-ID was deteted correctly GitHubMatch = Confidence{97, GitHubMatchStr, 2} + + // WPVulnDBMatch is a ranking how confident the CVE-ID was deteted correctly + WPVulnDBMatch = Confidence{100, WPVulnDBMatchStr, 0} ) diff --git a/models/vulninfos_test.go b/models/vulninfos_test.go index c142c84b..7666b64d 100644 --- a/models/vulninfos_test.go +++ b/models/vulninfos_test.go @@ -916,15 +916,15 @@ func TestFormatMaxCvssScore(t *testing.T) { func TestSortPackageStatues(t *testing.T) { var tests = []struct { - in PackageStatuses - out PackageStatuses + in PackageFixStatuses + out PackageFixStatuses }{ { - in: PackageStatuses{ + in: PackageFixStatuses{ {Name: "b"}, {Name: "a"}, }, - out: PackageStatuses{ + out: PackageFixStatuses{ {Name: "a"}, {Name: "b"}, }, @@ -940,19 +940,19 @@ func TestSortPackageStatues(t *testing.T) { func TestStorePackageStatueses(t *testing.T) { var tests = []struct { - pkgstats PackageStatuses - in PackageStatus - out PackageStatuses + pkgstats PackageFixStatuses + in PackageFixStatus + out PackageFixStatuses }{ { - pkgstats: PackageStatuses{ + pkgstats: PackageFixStatuses{ {Name: "a"}, {Name: "b"}, }, - in: PackageStatus{ + in: PackageFixStatus{ Name: "c", }, - out: PackageStatuses{ + out: PackageFixStatuses{ {Name: "a"}, {Name: "b"}, {Name: "c"}, diff --git a/models/wordpress.go b/models/wordpress.go new file mode 100644 index 00000000..06409f19 --- /dev/null +++ b/models/wordpress.go @@ -0,0 +1,88 @@ +/* Vuls - Vulnerability Scanner +Copyright (C) 2016 Future Corporation , Japan. + +This program is free software: you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation, either version 3 of the License, or +(at your option) any later version. + +This program is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with this program. If not, see . +*/ + +package models + +// WordPressPackages has Core version, plugins and themes. +type WordPressPackages []WpPackage + +// CoreVersion returns the core version of the installed WordPress +func (w WordPressPackages) CoreVersion() string { + for _, p := range w { + if p.Type == WPCore { + return p.Version + } + } + return "" +} + +// Plugins returns a slice of plugins of the installed WordPress +func (w WordPressPackages) Plugins() (ps []WpPackage) { + for _, p := range w { + if p.Type == WPPlugin { + ps = append(ps, p) + } + } + return +} + +// Themes returns a slice of themes of the installed WordPress +func (w WordPressPackages) Themes() (ps []WpPackage) { + for _, p := range w { + if p.Type == WPTheme { + ps = append(ps, p) + } + } + return +} + +// Find searches by specified name +func (w WordPressPackages) Find(name string) (ps *WpPackage, found bool) { + for _, p := range w { + if p.Name == name { + return &p, true + } + } + return nil, false +} + +const ( + // WPCore is a type `core` in WPPackage struct + WPCore = "core" + // WPPlugin is a type `plugin` in WPPackage struct + WPPlugin = "plugin" + // WPTheme is a type `theme` in WPPackage struct + WPTheme = "theme" + + // Inactive is a inactive status in WPPackage struct + Inactive = "inactive" +) + +// WpPackage has a details of plugin and theme +type WpPackage struct { + Name string `json:"name,omitempty"` + Status string `json:"status,omitempty"` // active, inactive or must-use + Update string `json:"update,omitempty"` // available or none + Version string `json:"version,omitempty"` + Type string `json:"type,omitempty"` // core, plugin, theme +} + +// WpPackageFixStatus is used in Vulninfo.WordPress +type WpPackageFixStatus struct { + Name string `json:"name,omitempty"` + FixedIn string `json:"fixedIn,omitempty"` +} diff --git a/oval/debian_test.go b/oval/debian_test.go index c3bf2b65..ca7e73cc 100644 --- a/oval/debian_test.go +++ b/oval/debian_test.go @@ -36,7 +36,7 @@ func TestPackNamesOfUpdateDebian(t *testing.T) { in: models.ScanResult{ ScannedCves: models.VulnInfos{ "CVE-2000-1000": models.VulnInfo{ - AffectedPackages: models.PackageStatuses{ + AffectedPackages: models.PackageFixStatuses{ {Name: "packA"}, {Name: "packC"}, }, @@ -56,7 +56,7 @@ func TestPackNamesOfUpdateDebian(t *testing.T) { out: models.ScanResult{ ScannedCves: models.VulnInfos{ "CVE-2000-1000": models.VulnInfo{ - AffectedPackages: models.PackageStatuses{ + AffectedPackages: models.PackageFixStatuses{ {Name: "packA"}, {Name: "packB", NotFixedYet: true}, {Name: "packC"}, diff --git a/oval/oval.go b/oval/oval.go index ab786f5d..4222cb46 100644 --- a/oval/oval.go +++ b/oval/oval.go @@ -28,6 +28,7 @@ import ( "github.com/future-architect/vuls/util" "github.com/kotakanbe/goval-dictionary/db" "github.com/parnurzeal/gorequest" + "golang.org/x/xerrors" ) // Client is the interface of OVAL client. @@ -58,7 +59,7 @@ func (b Base) CheckHTTPHealth() error { // resp, _, errs = gorequest.New().SetDebug(config.Conf.Debug).Get(url).End() // resp, _, errs = gorequest.New().Proxy(api.httpProxy).Get(url).End() if 0 < len(errs) || resp == nil || resp.StatusCode != 200 { - return fmt.Errorf("Failed to request to OVAL server. url: %s, errs: %v", + return xerrors.Errorf("Failed to request to OVAL server. url: %s, errs: %w", url, errs) } return nil @@ -69,8 +70,7 @@ func (b Base) CheckIfOvalFetched(driver db.DB, osFamily, release string) (fetche if !cnf.Conf.OvalDict.IsFetchViaHTTP() { count, err := driver.CountDefs(osFamily, release) if err != nil { - return false, fmt.Errorf("Failed to count OVAL defs: %s, %s, %v", - osFamily, release, err) + return false, xerrors.Errorf("Failed to count OVAL defs: %s, %s, %w", osFamily, release, err) } return 0 < count, nil } @@ -78,13 +78,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, fmt.Errorf("HTTP GET error: %v, url: %s, resp: %v", - errs, url, resp) + return false, xerrors.Errorf("HTTP GET error, url: %s, resp: %v, err: %w", url, resp, errs) } count := 0 if err := json.Unmarshal([]byte(body), &count); err != nil { - return false, fmt.Errorf("Failed to Unmarshall. body: %s, err: %s", - body, err) + return false, xerrors.Errorf("Failed to Unmarshall. body: %s, err: %w", body, err) } return 0 < count, nil } @@ -98,13 +96,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, fmt.Errorf("HTTP GET error: %v, url: %s, resp: %v", - errs, url, resp) + return false, xerrors.Errorf("HTTP GET error, url: %s, resp: %v, err: %w", url, resp, errs) } if err := json.Unmarshal([]byte(body), &lastModified); err != nil { - return false, fmt.Errorf("Failed to Unmarshall. body: %s, err: %s", - body, err) + return false, xerrors.Errorf("Failed to Unmarshall. body: %s, err: %w", body, err) } } diff --git a/oval/redhat_test.go b/oval/redhat_test.go index ac6ea7a6..59dc5b72 100644 --- a/oval/redhat_test.go +++ b/oval/redhat_test.go @@ -102,7 +102,7 @@ func TestPackNamesOfUpdate(t *testing.T) { in: models.ScanResult{ ScannedCves: models.VulnInfos{ "CVE-2000-1000": models.VulnInfo{ - AffectedPackages: models.PackageStatuses{ + AffectedPackages: models.PackageFixStatuses{ {Name: "packA"}, {Name: "packB", NotFixedYet: false}, }, @@ -126,7 +126,7 @@ func TestPackNamesOfUpdate(t *testing.T) { out: models.ScanResult{ ScannedCves: models.VulnInfos{ "CVE-2000-1000": models.VulnInfo{ - AffectedPackages: models.PackageStatuses{ + AffectedPackages: models.PackageFixStatuses{ {Name: "packA"}, {Name: "packB", NotFixedYet: true}, }, diff --git a/oval/util.go b/oval/util.go index 867f7c55..dec924f2 100644 --- a/oval/util.go +++ b/oval/util.go @@ -19,7 +19,6 @@ package oval import ( "encoding/json" - "fmt" "net/http" "regexp" "strings" @@ -34,6 +33,7 @@ import ( "github.com/kotakanbe/goval-dictionary/db" ovalmodels "github.com/kotakanbe/goval-dictionary/models" "github.com/parnurzeal/gorequest" + "golang.org/x/xerrors" ) type ovalResult struct { @@ -47,9 +47,9 @@ type defPacks struct { actuallyAffectedPackNames map[string]bool } -func (e defPacks) toPackStatuses() (ps models.PackageStatuses) { +func (e defPacks) toPackStatuses() (ps models.PackageFixStatuses) { for name, notFixedYet := range e.actuallyAffectedPackNames { - ps = append(ps, models.PackageStatus{ + ps = append(ps, models.PackageFixStatus{ Name: name, NotFixedYet: notFixedYet, }) @@ -164,11 +164,11 @@ func getDefsByPackNameViaHTTP(r *models.ScanResult) ( case err := <-errChan: errs = append(errs, err) case <-timeout: - return relatedDefs, fmt.Errorf("Timeout Fetching OVAL") + return relatedDefs, xerrors.New("Timeout Fetching OVAL") } } if len(errs) != 0 { - return relatedDefs, fmt.Errorf("Failed to fetch OVAL. err: %v", errs) + return relatedDefs, xerrors.Errorf("Failed to fetch OVAL. err: %w", errs) } return } @@ -186,8 +186,7 @@ func httpGet(url string, req request, resChan chan<- response, errChan chan<- er if count == retryMax { return nil } - return fmt.Errorf("HTTP GET error: %v, url: %s, resp: %v", - errs, url, resp) + return xerrors.Errorf("HTTP GET error, url: %s, resp: %v, err: %w", url, resp, errs) } return nil } @@ -196,18 +195,17 @@ func httpGet(url string, req request, resChan chan<- response, errChan chan<- er } err := backoff.RetryNotify(f, backoff.NewExponentialBackOff(), notify) if err != nil { - errChan <- fmt.Errorf("HTTP Error %s", err) + errChan <- xerrors.Errorf("HTTP Error %w", err) return } if count == retryMax { - errChan <- fmt.Errorf("HRetry count exceeded") + errChan <- xerrors.New("HRetry count exceeded") return } defs := []ovalmodels.Definition{} if err := json.Unmarshal([]byte(body), &defs); err != nil { - errChan <- fmt.Errorf("Failed to Unmarshall. body: %s, err: %s", - body, err) + errChan <- xerrors.Errorf("Failed to Unmarshall. body: %s, err: %w", body, err) return } resChan <- response{ @@ -238,7 +236,7 @@ func getDefsByPackNameFromOvalDB(driver db.DB, r *models.ScanResult) (relatedDef for _, req := range requests { definitions, err := driver.GetByPackName(r.Release, req.packName) if err != nil { - return relatedDefs, fmt.Errorf("Failed to get %s OVAL info by package: %#v, err: %s", r.Family, req, err) + 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 := isOvalDefAffected(def, req, r.Family, r.RunningKernel) @@ -349,5 +347,5 @@ func lessThan(family, versionRelease string, packB ovalmodels.Package) (bool, er default: util.Log.Errorf("Not implemented yet: %s", family) } - return false, fmt.Errorf("Package version comparison not supported: %s", family) + return false, xerrors.Errorf("Package version comparison not supported: %s", family) } diff --git a/oval/util_test.go b/oval/util_test.go index 4c999a73..0073e9b6 100644 --- a/oval/util_test.go +++ b/oval/util_test.go @@ -110,7 +110,7 @@ func TestDefpacksToPackStatuses(t *testing.T) { } var tests = []struct { in in - out models.PackageStatuses + out models.PackageFixStatuses }{ // Ubuntu { @@ -135,7 +135,7 @@ func TestDefpacksToPackStatuses(t *testing.T) { }, }, }, - out: models.PackageStatuses{ + out: models.PackageFixStatuses{ { Name: "a", NotFixedYet: true, diff --git a/report/azureblob.go b/report/azureblob.go index a466db8c..9dccfafd 100644 --- a/report/azureblob.go +++ b/report/azureblob.go @@ -25,6 +25,7 @@ import ( "time" storage "github.com/Azure/azure-sdk-for-go/storage" + "golang.org/x/xerrors" c "github.com/future-architect/vuls/config" "github.com/future-architect/vuls/models" @@ -60,7 +61,7 @@ func (w AzureBlobWriter) Write(rs ...models.ScanResult) (err error) { k := key + ".json" var b []byte if b, err = json.Marshal(r); err != nil { - return fmt.Errorf("Failed to Marshal to JSON: %s", err) + return xerrors.Errorf("Failed to Marshal to JSON: %w", err) } if err := createBlockBlob(cli, k, b); err != nil { return err @@ -87,7 +88,7 @@ func (w AzureBlobWriter) Write(rs ...models.ScanResult) (err error) { k := key + ".xml" var b []byte if b, err = xml.Marshal(r); err != nil { - return fmt.Errorf("Failed to Marshal to XML: %s", err) + 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 { @@ -117,7 +118,7 @@ func CheckIfAzureContainerExists() error { } } if !found { - return fmt.Errorf("Container not found. Container: %s", c.Conf.Azure.ContainerName) + return xerrors.Errorf("Container not found. Container: %s", c.Conf.Azure.ContainerName) } return nil } @@ -142,7 +143,7 @@ func createBlockBlob(cli storage.BlobStorageClient, k string, b []byte) error { ref := cli.GetContainerReference(c.Conf.Azure.ContainerName) blob := ref.GetBlobReference(k) if err := blob.CreateBlockBlobFromReader(bytes.NewReader(b), nil); err != nil { - return fmt.Errorf("Failed to upload data to %s/%s, %s", + return xerrors.Errorf("Failed to upload data to %s/%s, err: %w", c.Conf.Azure.ContainerName, k, err) } return nil diff --git a/report/cve_client.go b/report/cve_client.go index 2f7824ae..da081951 100644 --- a/report/cve_client.go +++ b/report/cve_client.go @@ -25,6 +25,7 @@ import ( "github.com/cenkalti/backoff" "github.com/parnurzeal/gorequest" + "golang.org/x/xerrors" "github.com/future-architect/vuls/config" "github.com/future-architect/vuls/util" @@ -57,7 +58,7 @@ func (api cvedictClient) CheckHealth() error { resp, _, errs = gorequest.New().SetDebug(config.Conf.Debug).Get(url).End() // resp, _, errs = gorequest.New().Proxy(api.httpProxy).Get(url).End() if 0 < len(errs) || resp == nil || resp.StatusCode != 200 { - return fmt.Errorf("Failed to request to CVE server. url: %s, errs: %v", + return xerrors.Errorf("Failed to request to CVE server. url: %s, errs: %w", url, errs) } return nil @@ -73,7 +74,7 @@ func (api cvedictClient) FetchCveDetails(driver cvedb.DB, cveIDs []string) (cveD for _, cveID := range cveIDs { cveDetail, err := driver.Get(cveID) if err != nil { - return nil, fmt.Errorf("Failed to fetch CVE. err: %s", err) + return nil, xerrors.Errorf("Failed to fetch CVE. err: %w", err) } if len(cveDetail.CveID) == 0 { cveDetails = append(cveDetails, cve.CveDetail{ @@ -132,12 +133,12 @@ func (api cvedictClient) FetchCveDetails(driver cvedb.DB, cveIDs []string) (cveD case err := <-errChan: errs = append(errs, err) case <-timeout: - return nil, fmt.Errorf("Timeout Fetching CVE") + return nil, xerrors.New("Timeout Fetching CVE") } } if len(errs) != 0 { return nil, - fmt.Errorf("Failed to fetch CVE. err: %v", errs) + xerrors.Errorf("Failed to fetch CVE. err: %w", errs) } return } @@ -150,8 +151,8 @@ 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 fmt.Errorf("HTTP GET error: %v, url: %s, resp: %v", - errs, url, resp) + return xerrors.Errorf("HTTP GET Error, url: %s, resp: %v, err: %w", + url, resp, errs) } return nil } @@ -161,13 +162,12 @@ func (api cvedictClient) httpGet(key, url string, resChan chan<- response, errCh } err := backoff.RetryNotify(f, backoff.NewExponentialBackOff(), notify) if err != nil { - errChan <- fmt.Errorf("HTTP Error %s", err) + errChan <- xerrors.Errorf("HTTP Error: %w", err) return } cveDetail := cve.CveDetail{} if err := json.Unmarshal([]byte(body), &cveDetail); err != nil { - errChan <- fmt.Errorf("Failed to Unmarshall. body: %s, err: %s", - body, err) + errChan <- xerrors.Errorf("Failed to Unmarshall. body: %s, err: %w", body, err) return } resChan <- response{ @@ -203,7 +203,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 fmt.Errorf("HTTP POST error: %v, url: %s, resp: %v", errs, url, resp) + return xerrors.Errorf("HTTP POST error. url: %s, resp: %v, err: %w", url, resp, errs) } return nil } @@ -212,13 +212,13 @@ func (api cvedictClient) httpPost(key, url string, query map[string]string) ([]c } err := backoff.RetryNotify(f, backoff.NewExponentialBackOff(), notify) if err != nil { - return nil, fmt.Errorf("HTTP Error %s", err) + return nil, xerrors.Errorf("HTTP Error: %w", err) } cveDetails := []cve.CveDetail{} if err := json.Unmarshal([]byte(body), &cveDetails); err != nil { return nil, - fmt.Errorf("Failed to Unmarshall. body: %s, err: %s", body, err) + xerrors.Errorf("Failed to Unmarshall. body: %s, err: %w", body, err) } return cveDetails, nil } diff --git a/report/db_client.go b/report/db_client.go index bee4b0a3..fbf4d45b 100644 --- a/report/db_client.go +++ b/report/db_client.go @@ -1,7 +1,6 @@ package report import ( - "fmt" "os" "github.com/future-architect/vuls/config" @@ -10,6 +9,7 @@ import ( cvedb "github.com/kotakanbe/go-cve-dictionary/db" ovaldb "github.com/kotakanbe/goval-dictionary/db" exploitdb "github.com/mozqnet/go-exploitdb/db" + "golang.org/x/xerrors" ) // DBClient is a dictionarie's db client for reporting @@ -33,7 +33,7 @@ type DBClientConf struct { func NewDBClient(cnf DBClientConf) (dbclient *DBClient, locked bool, err error) { cveDriver, locked, err := NewCveDB(cnf) if locked { - return nil, true, fmt.Errorf("CveDB is locked: %s", + return nil, true, xerrors.Errorf("CveDB is locked: %s", cnf.OvalDictCnf.SQLite3Path) } else if err != nil { return nil, locked, err @@ -41,7 +41,7 @@ func NewDBClient(cnf DBClientConf) (dbclient *DBClient, locked bool, err error) ovaldb, locked, err := NewOvalDB(cnf) if locked { - return nil, true, fmt.Errorf("OvalDB is locked: %s", + return nil, true, xerrors.Errorf("OvalDB is locked: %s", cnf.OvalDictCnf.SQLite3Path) } else if err != nil { util.Log.Warnf("Unable to use OvalDB: %s, err: %s", @@ -50,7 +50,7 @@ func NewDBClient(cnf DBClientConf) (dbclient *DBClient, locked bool, err error) gostdb, locked, err := NewGostDB(cnf) if locked { - return nil, true, fmt.Errorf("gostDB is locked: %s", + return nil, true, xerrors.Errorf("gostDB is locked: %s", cnf.GostCnf.SQLite3Path) } else if err != nil { util.Log.Warnf("Unable to use gostDB: %s, err: %s", @@ -59,7 +59,7 @@ func NewDBClient(cnf DBClientConf) (dbclient *DBClient, locked bool, err error) exploitdb, locked, err := NewExploitDB(cnf) if locked { - return nil, true, fmt.Errorf("exploitDB is locked: %s", + return nil, true, xerrors.Errorf("exploitDB is locked: %s", cnf.ExploitCnf.SQLite3Path) } else if err != nil { util.Log.Warnf("Unable to use exploitDB: %s, err: %s", @@ -88,7 +88,7 @@ func NewCveDB(cnf DBClientConf) (driver cvedb.DB, locked bool, err error) { util.Log.Debugf("Open cve-dictionary db (%s): %s", cnf.CveDictCnf.Type, path) driver, locked, err = cvedb.NewDB(cnf.CveDictCnf.Type, path, cnf.DebugSQL) if err != nil { - err = fmt.Errorf("Failed to init CVE DB. err: %s, path: %s", err, path) + err = xerrors.Errorf("Failed to init CVE DB. err: %w, path: %s", err, path) return nil, locked, err } return driver, false, nil @@ -112,7 +112,7 @@ func NewOvalDB(cnf DBClientConf) (driver ovaldb.DB, locked bool, err error) { util.Log.Debugf("Open oval-dictionary db (%s): %s", cnf.OvalDictCnf.Type, path) driver, locked, err = ovaldb.NewDB("", cnf.OvalDictCnf.Type, path, cnf.DebugSQL) if err != nil { - err = fmt.Errorf("Failed to new OVAL DB. err: %s", err) + err = xerrors.Errorf("Failed to new OVAL DB. err: %w", err) if locked { return nil, true, err } diff --git a/report/email.go b/report/email.go index 834a9731..a7160c54 100644 --- a/report/email.go +++ b/report/email.go @@ -27,6 +27,7 @@ import ( "github.com/future-architect/vuls/config" "github.com/future-architect/vuls/models" + "golang.org/x/xerrors" ) // EMailWriter send mail @@ -108,7 +109,7 @@ func (e *emailSender) Send(subject, body string) (err error) { cc := strings.Join(emailConf.Cc[:], ", ") mailAddresses := append(emailConf.To, emailConf.Cc...) if _, err := mail.ParseAddressList(strings.Join(mailAddresses[:], ", ")); err != nil { - return fmt.Errorf("Failed to parse email addresses: %s", err) + return xerrors.Errorf("Failed to parse email addresses: %w", err) } headers := make(map[string]string) @@ -141,7 +142,7 @@ func (e *emailSender) Send(subject, body string) (err error) { []byte(message), ) if err != nil { - return fmt.Errorf("Failed to send emails: %s", err) + return xerrors.Errorf("Failed to send emails: %w", err) } return nil } @@ -153,7 +154,7 @@ func (e *emailSender) Send(subject, body string) (err error) { []byte(message), ) if err != nil { - return fmt.Errorf("Failed to send emails: %s", err) + return xerrors.Errorf("Failed to send emails: %w", err) } return nil } diff --git a/report/http.go b/report/http.go index 980a3870..a31fa4e7 100644 --- a/report/http.go +++ b/report/http.go @@ -22,10 +22,9 @@ import ( "encoding/json" "net/http" - "github.com/pkg/errors" - c "github.com/future-architect/vuls/config" "github.com/future-architect/vuls/models" + "golang.org/x/xerrors" ) // HTTPRequestWriter writes results to HTTP request @@ -53,7 +52,7 @@ type HTTPResponseWriter struct { func (w HTTPResponseWriter) Write(rs ...models.ScanResult) (err error) { res, err := json.Marshal(rs) if err != nil { - return errors.Wrap(err, "Failed to marshal scah results") + return xerrors.Errorf("Failed to marshal scah results: %w", err) } w.Writer.Header().Set("Content-Type", "application/json") _, err = w.Writer.Write(res) diff --git a/report/localfile.go b/report/localfile.go index d658b935..032bc70b 100644 --- a/report/localfile.go +++ b/report/localfile.go @@ -21,13 +21,13 @@ import ( "bytes" "encoding/json" "encoding/xml" - "fmt" "io/ioutil" "os" "path/filepath" c "github.com/future-architect/vuls/config" "github.com/future-architect/vuls/models" + "golang.org/x/xerrors" ) // LocalFileWriter writes results to a local file. @@ -40,8 +40,8 @@ func (w LocalFileWriter) Write(rs ...models.ScanResult) (err error) { path := filepath.Join(w.CurrentDir, "summary.txt") text := formatOneLineSummary(rs...) if err := writeFile(path, []byte(text), 0600); err != nil { - return fmt.Errorf( - "Failed to write to file. path: %s, err: %s", + return xerrors.Errorf( + "Failed to write to file. path: %s, err: %w", path, err) } } @@ -60,15 +60,15 @@ func (w LocalFileWriter) Write(rs ...models.ScanResult) (err error) { var b []byte if c.Conf.Debug { if b, err = json.MarshalIndent(r, "", " "); err != nil { - return fmt.Errorf("Failed to Marshal to JSON: %s", err) + return xerrors.Errorf("Failed to Marshal to JSON: %w", err) } } else { if b, err = json.Marshal(r); err != nil { - return fmt.Errorf("Failed to Marshal to JSON: %s", err) + return xerrors.Errorf("Failed to Marshal to JSON: %w", err) } } if err := writeFile(p, b, 0600); err != nil { - return fmt.Errorf("Failed to write JSON. path: %s, err: %s", p, err) + return xerrors.Errorf("Failed to write JSON. path: %s, err: %w", p, err) } } @@ -82,8 +82,8 @@ func (w LocalFileWriter) Write(rs ...models.ScanResult) (err error) { if err := writeFile( p, []byte(formatList(r)), 0600); err != nil { - return fmt.Errorf( - "Failed to write text files. path: %s, err: %s", p, err) + return xerrors.Errorf( + "Failed to write text files. path: %s, err: %w", p, err) } } @@ -97,8 +97,8 @@ func (w LocalFileWriter) Write(rs ...models.ScanResult) (err error) { if err := writeFile( p, []byte(formatFullPlainText(r)), 0600); err != nil { - return fmt.Errorf( - "Failed to write text files. path: %s, err: %s", p, err) + return xerrors.Errorf( + "Failed to write text files. path: %s, err: %w", p, err) } } @@ -112,11 +112,11 @@ func (w LocalFileWriter) Write(rs ...models.ScanResult) (err error) { var b []byte if b, err = xml.Marshal(r); err != nil { - return fmt.Errorf("Failed to Marshal to XML: %s", err) + 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 fmt.Errorf("Failed to write XML. path: %s, err: %s", p, err) + return xerrors.Errorf("Failed to write XML. path: %s, err: %w", p, err) } } } diff --git a/report/report.go b/report/report.go index 27cd5a4e..c131c0d4 100644 --- a/report/report.go +++ b/report/report.go @@ -39,12 +39,13 @@ import ( "github.com/future-architect/vuls/models" "github.com/future-architect/vuls/oval" "github.com/future-architect/vuls/util" + "github.com/future-architect/vuls/wordpress" "github.com/hashicorp/uuid" gostdb "github.com/knqyf263/gost/db" cvedb "github.com/kotakanbe/go-cve-dictionary/db" ovaldb "github.com/kotakanbe/goval-dictionary/db" exploitdb "github.com/mozqnet/go-exploitdb/db" - "github.com/pkg/errors" + "golang.org/x/xerrors" ) const ( @@ -69,7 +70,7 @@ func FillCveInfos(dbclient DBClient, rs []models.ScanResult, dir string) ([]mode if owaspDCXMLPath != "" { cpes, err := parser.Parse(owaspDCXMLPath) if err != nil { - return nil, fmt.Errorf("Failed to read OWASP Dependency Check XML: %s, %s, %s", + return nil, xerrors.Errorf("Failed to read OWASP Dependency Check XML on %s, `%s`, err: %w", r.ServerName, owaspDCXMLPath, err) } cpeURIs = append(cpeURIs, cpes...) @@ -82,7 +83,7 @@ func FillCveInfos(dbclient DBClient, rs []models.ScanResult, dir string) ([]mode if owaspDCXMLPath != "" { cpes, err := parser.Parse(owaspDCXMLPath) if err != nil { - return nil, fmt.Errorf("Failed to read OWASP Dependency Check XML: %s, %s, %s", + return nil, xerrors.Errorf("Failed to read OWASP Dependency Check XML on %s, `%s`, err: %w", r.ServerInfo(), owaspDCXMLPath, err) } cpeURIs = append(cpeURIs, cpes...) @@ -91,8 +92,16 @@ func FillCveInfos(dbclient DBClient, rs []models.ScanResult, dir string) ([]mode } } + // Integrations githubInts := GithubSecurityAlerts(c.Conf.Servers[r.ServerName].GitHubRepos) - if err := FillCveInfo(dbclient, &r, cpeURIs, githubInts); err != nil { + + wpOpt := WordPressOption{c.Conf.Servers[r.ServerName].WordPress.WPVulnDBToken} + + if err := FillCveInfo(dbclient, + &r, + cpeURIs, + githubInts, + wpOpt); err != nil { return nil, err } r.Lang = c.Conf.Lang @@ -105,7 +114,7 @@ func FillCveInfos(dbclient DBClient, rs []models.ScanResult, dir string) ([]mode r.ServerName: c.Conf.Servers[r.ServerName], } if err := overwriteJSONFile(dir, r); err != nil { - return nil, fmt.Errorf("Failed to write JSON: %s", err) + return nil, xerrors.Errorf("Failed to write JSON: %w", err) } filledResults = append(filledResults, r) } else { @@ -139,6 +148,7 @@ func FillCveInfos(dbclient DBClient, rs []models.ScanResult, dir string) ([]mode r = r.FilterIgnoreCves() r = r.FilterUnfixed() r = r.FilterIgnorePkgs() + r = r.FilterInactiveWordPressLibs() if c.Conf.IgnoreUnscoredCves { r.ScannedCves = r.ScannedCves.FindScoredVulns() } @@ -153,7 +163,7 @@ func FillCveInfo(dbclient DBClient, r *models.ScanResult, cpeURIs []string, inte nCVEs, err := FillWithOval(dbclient.OvalDB, r) if err != nil { - return fmt.Errorf("Failed to fill with OVAL: %s", err) + return xerrors.Errorf("Failed to fill with OVAL: %w", err) } util.Log.Infof("%s: %d CVEs are detected with OVAL", r.FormatServerName(), nCVEs) @@ -169,11 +179,11 @@ func FillCveInfo(dbclient DBClient, r *models.ScanResult, cpeURIs []string, inte nCVEs, err = fillVulnByCpeURIs(dbclient.CveDB, r, cpeURIs) if err != nil { - return fmt.Errorf("Failed to detect vulns of %s: %s", cpeURIs, err) + 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 := &ints{} + ints := &integrationResults{} for _, o := range integrations { if err = o.apply(r, ints); err != nil { return err @@ -183,20 +193,20 @@ func FillCveInfo(dbclient DBClient, r *models.ScanResult, cpeURIs []string, inte nCVEs, err = FillWithGost(dbclient.GostDB, r) if err != nil { - return fmt.Errorf("Failed to fill with gost: %s", err) + 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") if err := fillCveDetail(dbclient.CveDB, r); err != nil { - return fmt.Errorf("Failed to fill with CVE: %s", err) + return xerrors.Errorf("Failed to fill with CVE: %w", err) } util.Log.Infof("Fill exploit information with Exploit-DB") nExploitCve, err := FillWithExploit(dbclient.ExploitDB, r) if err != nil { - return fmt.Errorf("Failed to fill with exploit: %s", err) + return xerrors.Errorf("Failed to fill with exploit: %w", err) } util.Log.Infof("%s: %d exploits are detected", r.FormatServerName(), nExploitCve) @@ -280,9 +290,9 @@ func FillWithOval(driver ovaldb.DB, r *models.ScanResult) (nCVEs int, err error) return 0, nil default: if r.Family == "" { - return 0, fmt.Errorf("Probably an error occurred during scanning. Check the error message") + return 0, xerrors.New("Probably an error occurred during scanning. Check the error message") } - return 0, fmt.Errorf("OVAL for %s is not implemented yet", r.Family) + return 0, xerrors.Errorf("OVAL for %s is not implemented yet", r.Family) } if !c.Conf.OvalDict.IsFetchViaHTTP() { @@ -290,7 +300,7 @@ func FillWithOval(driver ovaldb.DB, r *models.ScanResult) (nCVEs int, err error) return 0, nil } if err = driver.NewOvalDB(ovalFamily); err != nil { - return 0, fmt.Errorf("Failed to New Oval DB. err: %s", err) + return 0, xerrors.Errorf("Failed to New Oval DB. err: %w", err) } } @@ -356,13 +366,14 @@ func fillVulnByCpeURIs(driver cvedb.DB, r *models.ScanResult, cpeURIs []string) return nCVEs, nil } -type ints struct { +type integrationResults struct { GithubAlertsCveCounts int + WordPressCveCounts int } // Integration is integration of vuls report type Integration interface { - apply(*models.ScanResult, *ints) error + apply(*models.ScanResult, *integrationResults) error } // GithubSecurityAlerts : @@ -377,14 +388,15 @@ type GithubSecurityAlertOption struct { GithubConfs map[string]config.GitHubConf } -func (g GithubSecurityAlertOption) apply(r *models.ScanResult, ints *ints) (err error) { +// 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 errors.Wrap(err, "Failed to access GitHub Security Alerts") + return xerrors.Errorf("Failed to access GitHub Security Alerts: %w", err) } nCVEs += n } @@ -392,19 +404,21 @@ func (g GithubSecurityAlertOption) apply(r *models.ScanResult, ints *ints) (err return nil } -// https://help.github.com/articles/about-security-alerts-for-vulnerable-dependencies/ -func fillGitHubSecurityAlerts(r *models.ScanResult) (nCVEs int, err error) { - repos := c.Conf.Servers[r.ServerName].GitHubRepos - for ownerRepo, setting := range repos { - ss := strings.Split(ownerRepo, "/") - owner, repo := ss[0], ss[1] - n, err := github.FillGitHubSecurityAlerts(r, owner, repo, setting.Token) - if err != nil { - return 0, err - } - nCVEs += n +// WordPressOption : +type WordPressOption struct { + token string +} + +func (g WordPressOption) apply(r *models.ScanResult, ints *integrationResults) (err error) { + if g.token == "" { + return nil } - return nCVEs, nil + n, err := wordpress.FillWordPress(r, g.token) + if err != nil { + return xerrors.Errorf("Failed to fetch from WPVulnDB. Check the WPVulnDBToken in config.toml. err: %w", err) + } + ints.WordPressCveCounts = n + return nil } func fillCweDict(r *models.ScanResult) { @@ -420,9 +434,6 @@ func fillCweDict(r *models.ScanResult) { } } - // TODO check the format of CWEID, clean CWEID - // JVN, NVD XML, JSON, OVALs - dict := map[string]models.CweDictEntry{} for id := range uniqCweIDMap { entry := models.CweDictEntry{} @@ -638,21 +649,21 @@ func EnsureUUIDs(configPath string, results models.ScanResults) error { // rename the current config.toml to config.toml.bak info, err := os.Lstat(configPath) if err != nil { - return fmt.Errorf("Failed to lstat %s: %s", configPath, err) + 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 fmt.Errorf("Failed to Read link %s: %s", configPath, err) + return xerrors.Errorf("Failed to Read link %s: %w", configPath, err) } } if err := os.Rename(realPath, realPath+".bak"); err != nil { - return fmt.Errorf("Failed to rename %s: %s", configPath, err) + return xerrors.Errorf("Failed to rename %s: %w", configPath, err) } var buf bytes.Buffer if err := toml.NewEncoder(&buf).Encode(c); err != nil { - return fmt.Errorf("Failed to encode to toml: %s", err) + 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", diff --git a/report/s3.go b/report/s3.go index e72abaac..e120044a 100644 --- a/report/s3.go +++ b/report/s3.go @@ -31,6 +31,7 @@ import ( "github.com/aws/aws-sdk-go/aws/ec2metadata" "github.com/aws/aws-sdk-go/aws/session" "github.com/aws/aws-sdk-go/service/s3" + "golang.org/x/xerrors" c "github.com/future-architect/vuls/config" "github.com/future-architect/vuls/models" @@ -75,7 +76,7 @@ func (w S3Writer) Write(rs ...models.ScanResult) (err error) { k := key + ".json" var b []byte if b, err = json.Marshal(r); err != nil { - return fmt.Errorf("Failed to Marshal to JSON: %s", err) + return xerrors.Errorf("Failed to Marshal to JSON: %w", err) } if err := putObject(svc, k, b); err != nil { return err @@ -102,7 +103,7 @@ func (w S3Writer) Write(rs ...models.ScanResult) (err error) { k := key + ".xml" var b []byte if b, err = xml.Marshal(r); err != nil { - return fmt.Errorf("Failed to Marshal to XML: %s", err) + 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 { @@ -118,8 +119,8 @@ func CheckIfBucketExists() error { svc := getS3() result, err := svc.ListBuckets(&s3.ListBucketsInput{}) if err != nil { - return fmt.Errorf( - "Failed to list buckets. err: %s, profile: %s, region: %s", + return xerrors.Errorf( + "Failed to list buckets. err: %w, profile: %s, region: %s", err, c.Conf.AWS.Profile, c.Conf.AWS.Region) } @@ -131,7 +132,7 @@ func CheckIfBucketExists() error { } } if !found { - return fmt.Errorf( + return xerrors.Errorf( "Failed to find the buckets. profile: %s, region: %s, bucket: %s", c.Conf.AWS.Profile, c.Conf.AWS.Region, c.Conf.AWS.S3Bucket) } @@ -158,7 +159,7 @@ func putObject(svc *s3.S3, k string, b []byte) error { } if _, err := svc.PutObject(putObjectInput); err != nil { - return fmt.Errorf("Failed to upload data to %s/%s, %s", + return xerrors.Errorf("Failed to upload data to %s/%s, err: %w", c.Conf.AWS.S3Bucket, k, err) } return nil diff --git a/report/saas.go b/report/saas.go index 3f175ea5..50c746ca 100644 --- a/report/saas.go +++ b/report/saas.go @@ -37,6 +37,7 @@ import ( c "github.com/future-architect/vuls/config" "github.com/future-architect/vuls/models" "github.com/future-architect/vuls/util" + "golang.org/x/xerrors" ) // SaasWriter writes results to SaaS @@ -80,7 +81,7 @@ func (w SaasWriter) Write(rs ...models.ScanResult) (err error) { var body []byte if body, err = json.Marshal(payload); err != nil { - return fmt.Errorf("Failed to Marshal to JSON: %s", err) + return xerrors.Errorf("Failed to Marshal to JSON: %w", err) } var req *http.Request @@ -109,7 +110,7 @@ func (w SaasWriter) Write(rs ...models.ScanResult) (err error) { } defer resp.Body.Close() if resp.StatusCode != 200 { - return fmt.Errorf("Failed to get Credential. Request JSON : %s,", string(body)) + return xerrors.Errorf("Failed to get Credential. Request JSON : %s,", string(body)) } var t []byte @@ -119,7 +120,7 @@ func (w SaasWriter) Write(rs ...models.ScanResult) (err error) { var tempCredential TempCredential if err = json.Unmarshal(t, &tempCredential); err != nil { - return fmt.Errorf("Failed to unmarshal saas credential file. err : %s", err) + return xerrors.Errorf("Failed to unmarshal saas credential file. err : %s", err) } credential := credentials.NewStaticCredentialsFromCreds(credentials.Value{ @@ -133,7 +134,7 @@ func (w SaasWriter) Write(rs ...models.ScanResult) (err error) { Credentials: credential, Region: aws.String("ap-northeast-1"), }); err != nil { - return fmt.Errorf("Failed to new aws session. err : %s", err) + return xerrors.Errorf("Failed to new aws session. err: %w", err) } svc := s3.New(sess) @@ -141,7 +142,7 @@ func (w SaasWriter) Write(rs ...models.ScanResult) (err error) { s3Key := renameKeyNameUTC(r.ScannedAt, r.ServerUUID, r.Container) var b []byte if b, err = json.Marshal(r); err != nil { - return fmt.Errorf("Failed to Marshal to JSON: %s", err) + return xerrors.Errorf("Failed to Marshal to JSON: %w", err) } util.Log.Infof("Uploading...: ServerName: %s, ", r.ServerName) putObjectInput := &s3.PutObjectInput{ @@ -151,7 +152,7 @@ func (w SaasWriter) Write(rs ...models.ScanResult) (err error) { } if _, err := svc.PutObject(putObjectInput); err != nil { - return fmt.Errorf("Failed to upload data to %s/%s, %s", + return xerrors.Errorf("Failed to upload data to %s/%s, err: %w", tempCredential.S3Bucket, s3Key, err) } } diff --git a/report/slack.go b/report/slack.go index a1f20a16..e12b65ed 100644 --- a/report/slack.go +++ b/report/slack.go @@ -30,6 +30,7 @@ import ( "github.com/nlopes/slack" "github.com/parnurzeal/gorequest" log "github.com/sirupsen/logrus" + "golang.org/x/xerrors" ) type field struct { @@ -155,9 +156,9 @@ func send(msg message) error { if count == retryMax { return nil } - return fmt.Errorf( - "HTTP POST error: %v, url: %s, resp: %v, body: %s", - errs, conf.HookURL, resp, body) + return xerrors.Errorf( + "HTTP POST error. url: %s, resp: %v, body: %s, err: %w", + conf.HookURL, resp, body, errs) } return nil } @@ -167,10 +168,10 @@ func send(msg message) error { } boff := backoff.NewExponentialBackOff() if err := backoff.RetryNotify(f, boff, notify); err != nil { - return fmt.Errorf("HTTP error: %s", err) + return xerrors.Errorf("HTTP error: %w", err) } if count == retryMax { - return fmt.Errorf("Retry count exceeded") + return xerrors.New("Retry count exceeded") } return nil } @@ -202,39 +203,44 @@ func msgText(r models.ScanResult) string { func toSlackAttachments(r models.ScanResult) (attaches []slack.Attachment) { vinfos := r.ScannedCves.ToSortedSlice() for _, vinfo := range vinfos { - curent := []string{} + + installed, candidate := []string{}, []string{} for _, affected := range vinfo.AffectedPackages { if p, ok := r.Packages[affected.Name]; ok { - curent = append(curent, + installed = append(installed, fmt.Sprintf("%s-%s", p.Name, p.FormatVer())) } else { - curent = append(curent, affected.Name) + installed = append(installed, affected.Name) } - } - for _, n := range vinfo.CpeURIs { - curent = append(curent, n) - } - for _, n := range vinfo.GitHubSecurityAlerts { - curent = append(curent, n.PackageName) - } - new := []string{} - for _, affected := range vinfo.AffectedPackages { if p, ok := r.Packages[affected.Name]; ok { if affected.NotFixedYet { - new = append(new, "Not Fixed Yet") + candidate = append(candidate, "Not Fixed Yet") } else { - new = append(new, p.FormatNewVer()) + candidate = append(candidate, p.FormatNewVer()) } } else { - new = append(new, "?") + candidate = append(candidate, "?") } } - for range vinfo.CpeURIs { - new = append(new, "?") + + for _, n := range vinfo.CpeURIs { + installed = append(installed, n) + candidate = append(candidate, "?") } - for range vinfo.GitHubSecurityAlerts { - new = append(new, "?") + for _, n := range vinfo.GitHubSecurityAlerts { + installed = append(installed, n.PackageName) + candidate = append(candidate, "?") + } + + for _, wp := range vinfo.WpPackageFixStats { + if p, ok := r.WordPressPackages.Find(wp.Name); ok { + installed = append(installed, fmt.Sprintf("%s-%s", wp.Name, p.Version)) + candidate = append(candidate, wp.FixedIn) + } else { + installed = append(installed, wp.Name) + candidate = append(candidate, "?") + } } a := slack.Attachment{ @@ -246,12 +252,12 @@ func toSlackAttachments(r models.ScanResult) (attaches []slack.Attachment) { { // Title: "Current Package/CPE", Title: "Installed", - Value: strings.Join(curent, "\n"), + Value: strings.Join(installed, "\n"), Short: true, }, { Title: "Candidate", - Value: strings.Join(new, "\n"), + Value: strings.Join(candidate, "\n"), Short: true, }, }, @@ -269,7 +275,7 @@ func cvssColor(cvssScore float64) string { return "danger" case 4 <= cvssScore && cvssScore < 7: return "warning" - case cvssScore < 0: + case cvssScore == 0: return "#C0C0C0" default: return "good" diff --git a/report/syslog.go b/report/syslog.go index bd565d79..18155ac6 100644 --- a/report/syslog.go +++ b/report/syslog.go @@ -22,8 +22,7 @@ import ( "strings" syslog "github.com/RackSec/srslog" - - "github.com/pkg/errors" + "golang.org/x/xerrors" "github.com/future-architect/vuls/config" "github.com/future-architect/vuls/models" @@ -40,7 +39,7 @@ func (w SyslogWriter) Write(rs ...models.ScanResult) (err error) { sysLog, err := syslog.Dial(conf.Protocol, raddr, severity|facility, conf.Tag) if err != nil { - return errors.Wrap(err, "Failed to initialize syslog client") + return xerrors.Errorf("Failed to initialize syslog client: %w", err) } for _, r := range rs { diff --git a/report/syslog_test.go b/report/syslog_test.go index 40cc9fc6..e9bdba43 100644 --- a/report/syslog_test.go +++ b/report/syslog_test.go @@ -22,15 +22,15 @@ func TestSyslogWriterEncodeSyslog(t *testing.T) { IPv4Addrs: []string{"192.168.0.1", "10.0.2.15"}, ScannedCves: models.VulnInfos{ "CVE-2017-0001": models.VulnInfo{ - AffectedPackages: models.PackageStatuses{ - models.PackageStatus{Name: "pkg1"}, - models.PackageStatus{Name: "pkg2"}, + AffectedPackages: models.PackageFixStatuses{ + models.PackageFixStatus{Name: "pkg1"}, + models.PackageFixStatus{Name: "pkg2"}, }, }, "CVE-2017-0002": models.VulnInfo{ - AffectedPackages: models.PackageStatuses{ - models.PackageStatus{Name: "pkg3"}, - models.PackageStatus{Name: "pkg4"}, + AffectedPackages: models.PackageFixStatuses{ + models.PackageFixStatus{Name: "pkg3"}, + models.PackageFixStatus{Name: "pkg4"}, }, CveContents: models.CveContents{ models.NvdXML: models.CveContent{ @@ -57,8 +57,8 @@ func TestSyslogWriterEncodeSyslog(t *testing.T) { IPv6Addrs: []string{"2001:0DB8::1"}, ScannedCves: models.VulnInfos{ "CVE-2017-0003": models.VulnInfo{ - AffectedPackages: models.PackageStatuses{ - models.PackageStatus{Name: "pkg5"}, + AffectedPackages: models.PackageFixStatuses{ + models.PackageFixStatus{Name: "pkg5"}, }, CveContents: models.CveContents{ models.RedHat: models.CveContent{ diff --git a/report/telegram.go b/report/telegram.go index 9cad68c7..b3551e1f 100644 --- a/report/telegram.go +++ b/report/telegram.go @@ -9,6 +9,7 @@ import ( "github.com/future-architect/vuls/config" "github.com/future-architect/vuls/models" + "golang.org/x/xerrors" ) // TelegramWriter sends report to Telegram @@ -73,5 +74,5 @@ func checkResponse(r *http.Response) error { if c := r.StatusCode; 200 <= c && c <= 299 { return nil } - return fmt.Errorf("API call to %s failed: %s", r.Request.URL.String(), r.Status) + return xerrors.Errorf("API call to %s failed: %s", r.Request.URL.String(), r.Status) } diff --git a/report/tui.go b/report/tui.go index c7b6f5d1..0e3b4105 100644 --- a/report/tui.go +++ b/report/tui.go @@ -27,6 +27,7 @@ import ( "time" "github.com/future-architect/vuls/alert" + "golang.org/x/xerrors" "github.com/future-architect/vuls/config" "github.com/future-architect/vuls/models" @@ -581,7 +582,7 @@ func setSideLayout(g *gocui.Gui) error { fmt.Fprintln(v, result.ServerInfoTui()) } if len(scanResults) == 0 { - return fmt.Errorf("No scan results") + return xerrors.New("No scan results") } currentScanResult = scanResults[0] vinfos = scanResults[0].ScannedCves.ToSortedSlice() @@ -634,9 +635,10 @@ func summaryLines(r models.ScanResult) string { cvssScore = fmt.Sprintf("| %4.1f", max) } - packname := vinfo.AffectedPackages.FormatTuiSummary() - packname += strings.Join(vinfo.CpeURIs, ", ") - packname += vinfo.GitHubSecurityAlerts.String() + pkgNames := vinfo.AffectedPackages.Names() + pkgNames = append(pkgNames, vinfo.CpeURIs...) + pkgNames = append(pkgNames, vinfo.GitHubSecurityAlerts.Names()...) + pkgNames = append(pkgNames, vinfo.WpPackageFixStats.Names()...) alert := " " if vinfo.AlertDict.HasAlert() { @@ -648,9 +650,9 @@ func summaryLines(r models.ScanResult) string { fmt.Sprintf(indexFormat, i+1), alert + vinfo.CveID, cvssScore + " |", - fmt.Sprintf("%8s |", vinfo.AttackVector()), + fmt.Sprintf("%1s |", vinfo.AttackVector()), fmt.Sprintf("%7s |", vinfo.PatchStatus(r.Packages)), - packname, + strings.Join(pkgNames, ", "), } icols := make([]interface{}, len(cols)) for j := range cols { @@ -747,6 +749,22 @@ func setChangelogLayout(g *gocui.Gui) error { lines = append(lines, "* "+alert.PackageName) } + r := currentScanResult + for _, wp := range vinfo.WpPackageFixStats { + if p, ok := r.WordPressPackages.Find(wp.Name); ok { + if p.Type == models.WPCore { + lines = append(lines, fmt.Sprintf("* %s-%s, FixedIn: %s", + wp.Name, p.Version, wp.FixedIn)) + } else { + lines = append(lines, + fmt.Sprintf("* %s-%s, Update: %s, FixedIn: %s, %s", + wp.Name, p.Version, p.Update, wp.FixedIn, p.Status)) + } + } else { + lines = append(lines, fmt.Sprintf("* %s", wp.Name)) + } + } + for _, adv := range vinfo.DistroAdvisories { lines = append(lines, "\n", "Advisories", @@ -846,10 +864,13 @@ func detailLines() (string, error) { } vinfo := vinfos[currentVinfo] - links := []string{vinfo.CveContents.SourceLinks( - config.Conf.Lang, r.Family, vinfo.CveID)[0].Value, - vinfo.Cvss2CalcURL(), - vinfo.Cvss3CalcURL()} + 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) } diff --git a/report/util.go b/report/util.go index aaab0649..7b6d9591 100644 --- a/report/util.go +++ b/report/util.go @@ -35,6 +35,7 @@ import ( "github.com/future-architect/vuls/util" "github.com/gosuri/uitable" "github.com/olekukonko/tablewriter" + "golang.org/x/xerrors" ) const maxColWidth = 100 @@ -121,17 +122,23 @@ No CVE-IDs are found in updatable packages. exploits = " Y" } + link := "" + if strings.HasPrefix(vinfo.CveID, "CVE-") { + link = fmt.Sprintf("https://nvd.nist.gov/vuln/detail/%s", vinfo.CveID) + } else if strings.HasPrefix(vinfo.CveID, "WPVDBID-") { + link = fmt.Sprintf("https://wpvulndb.com/vulnerabilities/%s", strings.TrimPrefix(vinfo.CveID, "WPVDBID-")) + } + data = append(data, []string{ vinfo.CveID, + fmt.Sprintf("%7s", vinfo.PatchStatus(r.Packages)), vinfo.AlertDict.FormatSource(), fmt.Sprintf("%4.1f", max), // fmt.Sprintf("%4.1f", v2max), // fmt.Sprintf("%4.1f", v3max), - fmt.Sprintf("%8s", vinfo.AttackVector()), - fmt.Sprintf("%7s", vinfo.PatchStatus(r.Packages)), - // packname, - fmt.Sprintf("https://nvd.nist.gov/vuln/detail/%s", vinfo.CveID), + fmt.Sprintf("%2s", vinfo.AttackVector()), exploits, + link, }) } @@ -139,15 +146,14 @@ No CVE-IDs are found in updatable packages. table := tablewriter.NewWriter(&b) table.SetHeader([]string{ "CVE-ID", + "Fixed", "CERT", "CVSS", // "v3", // "v2", - "Attack", - "Fixed", - // "Pkg", + "AV", + "PoC", "NVD", - "Exploit", }) table.SetBorder(true) table.AppendBulk(data) @@ -243,19 +249,37 @@ No CVE-IDs are found in updatable packages. data = append(data, []string{"GitHub", alert.PackageName}) } + for _, wp := range vuln.WpPackageFixStats { + if p, ok := r.WordPressPackages.Find(wp.Name); ok { + if p.Type == models.WPCore { + data = append(data, []string{"WordPress", + fmt.Sprintf("%s-%s, FixedIn: %s", wp.Name, p.Version, wp.FixedIn)}) + } else { + data = append(data, []string{"WordPress", + fmt.Sprintf("%s-%s, Update: %s, FixedIn: %s, %s", + wp.Name, p.Version, p.Update, wp.FixedIn, p.Status)}) + } + } else { + data = append(data, []string{"WordPress", + fmt.Sprintf("%s", wp.Name)}) + } + } + for _, confidence := range vuln.Confidences { data = append(data, []string{"Confidence", confidence.String()}) } - links := vuln.CveContents.SourceLinks( - config.Conf.Lang, r.Family, vuln.CveID) - data = append(data, []string{"Source", links[0].Value}) + if 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()}) - } - if 0 < len(vuln.Cvss3Scores()) { - data = append(data, []string{"CVSSv3 Calc", vuln.Cvss3CalcURL()}) + if 0 < len(vuln.Cvss2Scores(r.Family)) { + data = append(data, []string{"CVSSv2 Calc", vuln.Cvss2CalcURL()}) + } + if 0 < len(vuln.Cvss3Scores()) { + data = append(data, []string{"CVSSv3 Calc", vuln.Cvss3CalcURL()}) + } } vlinks := vuln.VendorLinks(r.Family) @@ -292,7 +316,7 @@ No CVE-IDs are found in updatable packages. table.SetHeaderAlignment(tablewriter.ALIGN_LEFT) table.SetHeader([]string{ vuln.CveID, - "", + vuln.PatchStatus(r.Packages), }) table.SetBorder(true) table.AppendBulk(data) @@ -353,7 +377,7 @@ func overwriteJSONFile(dir string, r models.ScanResult) error { config.Conf.Diff = false w := LocalFileWriter{CurrentDir: dir} if err := w.Write(r); err != nil { - return fmt.Errorf("Failed to write summary report: %s", err) + return xerrors.Errorf("Failed to write summary report: %w", err) } config.Conf.FormatJSON = before config.Conf.Diff = beforeDiff @@ -521,7 +545,7 @@ var jsonDirPattern = regexp.MustCompile( func ListValidJSONDirs() (dirs []string, err error) { var dirInfo []os.FileInfo if dirInfo, err = ioutil.ReadDir(config.Conf.ResultsDir); err != nil { - err = fmt.Errorf("Failed to read %s: %s", + err = xerrors.Errorf("Failed to read %s: %w", config.Conf.ResultsDir, err) return } @@ -559,20 +583,20 @@ func JSONDir(args []string) (string, error) { } } - return "", fmt.Errorf("Invalid path: %s", path) + return "", xerrors.Errorf("Invalid path: %s", path) } // PIPE if config.Conf.Pipe { bytes, err := ioutil.ReadAll(os.Stdin) if err != nil { - return "", fmt.Errorf("Failed to read stdin: %s", err) + return "", xerrors.Errorf("Failed to read stdin: %w", err) } fields := strings.Fields(string(bytes)) if 0 < len(fields) { return filepath.Join(config.Conf.ResultsDir, fields[0]), nil } - return "", fmt.Errorf("Stdin is invalid: %s", string(bytes)) + return "", xerrors.Errorf("Stdin is invalid: %s", string(bytes)) } // returns latest dir when no args or no PIPE @@ -580,7 +604,7 @@ func JSONDir(args []string) (string, error) { return "", err } if len(dirs) == 0 { - return "", fmt.Errorf("No results under %s", + return "", xerrors.Errorf("No results under %s", config.Conf.ResultsDir) } return dirs[0], nil @@ -590,7 +614,7 @@ func JSONDir(args []string) (string, error) { func LoadScanResults(jsonDir string) (results models.ScanResults, err error) { var files []os.FileInfo if files, err = ioutil.ReadDir(jsonDir); err != nil { - return nil, fmt.Errorf("Failed to read %s: %s", jsonDir, err) + return nil, xerrors.Errorf("Failed to read %s: %w", jsonDir, err) } for _, f := range files { if filepath.Ext(f.Name()) != ".json" || strings.HasSuffix(f.Name(), "_diff.json") { @@ -605,7 +629,7 @@ func LoadScanResults(jsonDir string) (results models.ScanResults, err error) { results = append(results, *r) } if len(results) == 0 { - return nil, fmt.Errorf("There is no json file under %s", jsonDir) + return nil, xerrors.Errorf("There is no json file under %s", jsonDir) } return } @@ -617,11 +641,11 @@ func loadOneServerScanResult(jsonFile string) (*models.ScanResult, error) { err error ) if data, err = ioutil.ReadFile(jsonFile); err != nil { - return nil, fmt.Errorf("Failed to read %s: %s", jsonFile, err) + return nil, xerrors.Errorf("Failed to read %s: %w", jsonFile, err) } result := &models.ScanResult{} if err := json.Unmarshal(data, result); err != nil { - return nil, fmt.Errorf("Failed to parse %s: %s", jsonFile, err) + return nil, xerrors.Errorf("Failed to parse %s: %w", jsonFile, err) } return result, nil } diff --git a/report/util_test.go b/report/util_test.go index 56e6d155..f463a533 100644 --- a/report/util_test.go +++ b/report/util_test.go @@ -192,13 +192,13 @@ func TestDiff(t *testing.T) { ScannedCves: models.VulnInfos{ "CVE-2012-6702": { CveID: "CVE-2012-6702", - AffectedPackages: models.PackageStatuses{{Name: "libexpat1"}}, + AffectedPackages: models.PackageFixStatuses{{Name: "libexpat1"}}, DistroAdvisories: []models.DistroAdvisory{}, CpeURIs: []string{}, }, "CVE-2014-9761": { CveID: "CVE-2014-9761", - AffectedPackages: models.PackageStatuses{{Name: "libc-bin"}}, + AffectedPackages: models.PackageFixStatuses{{Name: "libc-bin"}}, DistroAdvisories: []models.DistroAdvisory{}, CpeURIs: []string{}, }, @@ -217,13 +217,13 @@ func TestDiff(t *testing.T) { ScannedCves: models.VulnInfos{ "CVE-2012-6702": { CveID: "CVE-2012-6702", - AffectedPackages: models.PackageStatuses{{Name: "libexpat1"}}, + AffectedPackages: models.PackageFixStatuses{{Name: "libexpat1"}}, DistroAdvisories: []models.DistroAdvisory{}, CpeURIs: []string{}, }, "CVE-2014-9761": { CveID: "CVE-2014-9761", - AffectedPackages: models.PackageStatuses{{Name: "libc-bin"}}, + AffectedPackages: models.PackageFixStatuses{{Name: "libc-bin"}}, DistroAdvisories: []models.DistroAdvisory{}, CpeURIs: []string{}, }, @@ -254,7 +254,7 @@ func TestDiff(t *testing.T) { ScannedCves: models.VulnInfos{ "CVE-2016-6662": { CveID: "CVE-2016-6662", - AffectedPackages: models.PackageStatuses{{Name: "mysql-libs"}}, + AffectedPackages: models.PackageFixStatuses{{Name: "mysql-libs"}}, DistroAdvisories: []models.DistroAdvisory{}, CpeURIs: []string{}, }, @@ -292,7 +292,7 @@ func TestDiff(t *testing.T) { ScannedCves: models.VulnInfos{ "CVE-2016-6662": { CveID: "CVE-2016-6662", - AffectedPackages: models.PackageStatuses{{Name: "mysql-libs"}}, + AffectedPackages: models.PackageFixStatuses{{Name: "mysql-libs"}}, DistroAdvisories: []models.DistroAdvisory{}, CpeURIs: []string{}, }, @@ -348,7 +348,7 @@ func TestIsCveFixed(t *testing.T) { in: In{ v: models.VulnInfo{ CveID: "CVE-2016-6662", - AffectedPackages: models.PackageStatuses{ + AffectedPackages: models.PackageFixStatuses{ { Name: "mysql-libs", NotFixedYet: false, @@ -366,7 +366,7 @@ func TestIsCveFixed(t *testing.T) { ScannedCves: models.VulnInfos{ "CVE-2016-6662": { CveID: "CVE-2016-6662", - AffectedPackages: models.PackageStatuses{ + AffectedPackages: models.PackageFixStatuses{ { Name: "mysql-libs", NotFixedYet: true, @@ -389,7 +389,7 @@ func TestIsCveFixed(t *testing.T) { in: In{ v: models.VulnInfo{ CveID: "CVE-2016-6662", - AffectedPackages: models.PackageStatuses{ + AffectedPackages: models.PackageFixStatuses{ { Name: "mysql-libs", NotFixedYet: true, @@ -407,7 +407,7 @@ func TestIsCveFixed(t *testing.T) { ScannedCves: models.VulnInfos{ "CVE-2016-6662": { CveID: "CVE-2016-6662", - AffectedPackages: models.PackageStatuses{ + AffectedPackages: models.PackageFixStatuses{ { Name: "mysql-libs", NotFixedYet: true, diff --git a/scan/alpine.go b/scan/alpine.go index 29a8eaad..30ce051f 100644 --- a/scan/alpine.go +++ b/scan/alpine.go @@ -19,12 +19,12 @@ package scan import ( "bufio" - "fmt" "strings" "github.com/future-architect/vuls/config" "github.com/future-architect/vuls/models" "github.com/future-architect/vuls/util" + "golang.org/x/xerrors" ) // inherit OsTypeInterface @@ -66,7 +66,7 @@ func detectAlpine(c config.ServerInfo) (itsMe bool, os osTypeInterface) { func (o *alpine) checkScanMode() error { if o.getServerInfo().Mode.IsOffline() { - return fmt.Errorf("Remove offline scan mode, Alpine needs internet connection") + return xerrors.New("Remove offline scan mode, Alpine needs internet connection") } return nil } @@ -84,7 +84,7 @@ func (o *alpine) checkIfSudoNoPasswd() error { func (o *alpine) apkUpdate() error { r := o.exec("apk update", noSudo) if !r.isSuccess() { - return fmt.Errorf("Failed to SSH: %s", r) + return xerrors.Errorf("Failed to SSH: %s", r) } return nil } @@ -143,7 +143,7 @@ func (o *alpine) scanInstalledPackages() (models.Packages, error) { cmd := util.PrependProxyEnv("apk info -v") r := o.exec(cmd, noSudo) if !r.isSuccess() { - return nil, fmt.Errorf("Failed to SSH: %s", r) + return nil, xerrors.Errorf("Failed to SSH: %s", r) } return o.parseApkInfo(r.Stdout) } @@ -160,7 +160,7 @@ func (o *alpine) parseApkInfo(stdout string) (models.Packages, error) { line := scanner.Text() ss := strings.Split(line, "-") if len(ss) < 3 { - return nil, fmt.Errorf("Failed to parse apk info -v: %s", line) + return nil, xerrors.Errorf("Failed to parse apk info -v: %s", line) } name := strings.Join(ss[:len(ss)-2], "-") packs[name] = models.Package{ @@ -175,7 +175,7 @@ func (o *alpine) scanUpdatablePackages() (models.Packages, error) { cmd := util.PrependProxyEnv("apk version") r := o.exec(cmd, noSudo) if !r.isSuccess() { - return nil, fmt.Errorf("Failed to SSH: %s", r) + return nil, xerrors.Errorf("Failed to SSH: %s", r) } return o.parseApkVersion(r.Stdout) } diff --git a/scan/amazon.go b/scan/amazon.go index 0befbba0..63fd4597 100644 --- a/scan/amazon.go +++ b/scan/amazon.go @@ -1,11 +1,10 @@ package scan import ( - "fmt" - "github.com/future-architect/vuls/config" "github.com/future-architect/vuls/models" "github.com/future-architect/vuls/util" + "golang.org/x/xerrors" ) // inherit OsTypeInterface @@ -33,7 +32,7 @@ func newAmazon(c config.ServerInfo) *amazon { func (o *amazon) checkScanMode() error { if o.getServerInfo().Mode.IsOffline() { - return fmt.Errorf("Remove offline scan mode, Amazon needs internet connection") + return xerrors.New("Remove offline scan mode, Amazon needs internet connection") } return nil } @@ -46,7 +45,7 @@ func (o *amazon) checkDeps() error { } else if o.getServerInfo().Mode.IsDeep() { return o.execCheckDeps(o.depsDeep()) } - return fmt.Errorf("Unknown scan mode") + return xerrors.New("Unknown scan mode") } func (o *amazon) depsFast() []string { diff --git a/scan/base.go b/scan/base.go index 3a1eb465..9f2543af 100644 --- a/scan/base.go +++ b/scan/base.go @@ -19,6 +19,7 @@ package scan import ( "bufio" + "encoding/json" "fmt" "net" "regexp" @@ -28,6 +29,7 @@ import ( "github.com/future-architect/vuls/config" "github.com/future-architect/vuls/models" "github.com/sirupsen/logrus" + "golang.org/x/xerrors" ) type base struct { @@ -35,6 +37,7 @@ type base struct { Distro config.Distro Platform models.Platform osPackages + WordPress *models.WordPressPackages log *logrus.Entry errs []error @@ -79,7 +82,7 @@ func (l *base) getPlatform() models.Platform { func (l *base) runningKernel() (release, version string, err error) { r := l.exec("uname -r", noSudo) if !r.isSuccess() { - return "", "", fmt.Errorf("Failed to SSH: %s", r) + return "", "", xerrors.Errorf("Failed to SSH: %s", r) } release = strings.TrimSpace(r.Stdout) @@ -87,7 +90,7 @@ func (l *base) runningKernel() (release, version string, err error) { case config.Debian: r := l.exec("uname -a", noSudo) if !r.isSuccess() { - return "", "", fmt.Errorf("Failed to SSH: %s", r) + return "", "", xerrors.Errorf("Failed to SSH: %s", r) } ss := strings.Fields(r.Stdout) if 6 < len(ss) { @@ -118,7 +121,7 @@ func (l *base) allContainers() (containers []config.Container, err error) { } return l.parseLxcPs(stdout) default: - return containers, fmt.Errorf( + return containers, xerrors.Errorf( "Not supported yet: %s", l.ServerInfo.ContainerType) } } @@ -144,7 +147,7 @@ func (l *base) runningContainers() (containers []config.Container, err error) { } return l.parseLxcPs(stdout) default: - return containers, fmt.Errorf( + return containers, xerrors.Errorf( "Not supported yet: %s", l.ServerInfo.ContainerType) } } @@ -170,7 +173,7 @@ func (l *base) exitedContainers() (containers []config.Container, err error) { } return l.parseLxcPs(stdout) default: - return containers, fmt.Errorf( + return containers, xerrors.Errorf( "Not supported yet: %s", l.ServerInfo.ContainerType) } } @@ -179,7 +182,7 @@ func (l *base) dockerPs(option string) (string, error) { cmd := fmt.Sprintf("docker ps %s", option) r := l.exec(cmd, noSudo) if !r.isSuccess() { - return "", fmt.Errorf("Failed to SSH: %s", r) + return "", xerrors.Errorf("Failed to SSH: %s", r) } return r.Stdout, nil } @@ -188,7 +191,7 @@ func (l *base) lxdPs(option string) (string, error) { cmd := fmt.Sprintf("lxc list %s", option) r := l.exec(cmd, noSudo) if !r.isSuccess() { - return "", fmt.Errorf("failed to SSH: %s", r) + return "", xerrors.Errorf("failed to SSH: %s", r) } return r.Stdout, nil } @@ -197,7 +200,7 @@ func (l *base) lxcPs(option string) (string, error) { cmd := fmt.Sprintf("lxc-ls %s 2>/dev/null", option) r := l.exec(cmd, sudo) if !r.isSuccess() { - return "", fmt.Errorf("failed to SSH: %s", r) + return "", xerrors.Errorf("failed to SSH: %s", r) } return r.Stdout, nil } @@ -210,7 +213,7 @@ func (l *base) parseDockerPs(stdout string) (containers []config.Container, err break } if len(fields) != 3 { - return containers, fmt.Errorf("Unknown format: %s", line) + return containers, xerrors.Errorf("Unknown format: %s", line) } containers = append(containers, config.Container{ ContainerID: fields[0], @@ -232,7 +235,7 @@ func (l *base) parseLxdPs(stdout string) (containers []config.Container, err err break } if len(fields) != 1 { - return containers, fmt.Errorf("Unknown format: %s", line) + return containers, xerrors.Errorf("Unknown format: %s", line) } containers = append(containers, config.Container{ ContainerID: fields[0], @@ -265,7 +268,7 @@ func (l *base) ip() ([]string, []string, error) { // 2: eth0 inet6 fe80::5054:ff:fe2a:864c/64 scope link \ valid_lft forever preferred_lft forever r := l.exec("/sbin/ip -o addr", noSudo) if !r.isSuccess() { - return nil, nil, fmt.Errorf("Failed to detect IP address: %v", r) + return nil, nil, xerrors.Errorf("Failed to detect IP address: %v", r) } ipv4Addrs, ipv6Addrs := l.parseIP(r.Stdout) return ipv4Addrs, ipv6Addrs, nil @@ -358,7 +361,7 @@ func (l *base) detectRunningOnAws() (ok bool, instanceID string, err error) { return false, "", nil } } - return false, "", fmt.Errorf( + return false, "", xerrors.Errorf( "Failed to curl or wget to AWS instance metadata on %s. container: %s", l.ServerInfo.ServerName, l.ServerInfo.Container.Name) } @@ -388,22 +391,23 @@ func (l *base) convertToModel() models.ScanResult { } return models.ScanResult{ - JSONVersion: models.JSONVersion, - ServerName: l.ServerInfo.ServerName, - ScannedAt: time.Now(), - ScanMode: l.ServerInfo.Mode.String(), - Family: l.Distro.Family, - Release: l.Distro.Release, - Container: container, - Platform: l.Platform, - IPv4Addrs: l.ServerInfo.IPv4Addrs, - IPv6Addrs: l.ServerInfo.IPv6Addrs, - ScannedCves: l.VulnInfos, - RunningKernel: l.Kernel, - Packages: l.Packages, - SrcPackages: l.SrcPackages, - Optional: l.ServerInfo.Optional, - Errors: errs, + JSONVersion: models.JSONVersion, + ServerName: l.ServerInfo.ServerName, + ScannedAt: time.Now(), + ScanMode: l.ServerInfo.Mode.String(), + Family: l.Distro.Family, + Release: l.Distro.Release, + Container: container, + Platform: l.Platform, + IPv4Addrs: l.ServerInfo.IPv4Addrs, + IPv6Addrs: l.ServerInfo.IPv6Addrs, + ScannedCves: l.VulnInfos, + RunningKernel: l.Kernel, + Packages: l.Packages, + SrcPackages: l.SrcPackages, + WordPressPackages: l.WordPress, + Optional: l.ServerInfo.Optional, + Errors: errs, } } @@ -427,7 +431,7 @@ func (l *base) detectInitSystem() (string, error) { f = func(cmd string) (string, error) { r := l.exec(cmd, sudo) if !r.isSuccess() { - return "", fmt.Errorf("Failed to stat %s: %s", cmd, r) + return "", xerrors.Errorf("Failed to stat %s: %s", cmd, r) } scanner := bufio.NewScanner(strings.NewReader(r.Stdout)) scanner.Scan() @@ -449,7 +453,7 @@ func (l *base) detectInitSystem() (string, error) { } return sysVinit, nil } - return "", fmt.Errorf("Failed to detect a init system: %s", line) + return "", xerrors.Errorf("Failed to detect a init system: %s", line) } return f("stat /proc/1/exe") } @@ -458,7 +462,7 @@ func (l *base) detectServiceName(pid string) (string, error) { cmd := fmt.Sprintf("systemctl status --quiet --no-pager %s", pid) r := l.exec(cmd, noSudo) if !r.isSuccess() { - return "", fmt.Errorf("Failed to stat %s: %s", cmd, r) + return "", xerrors.Errorf("Failed to stat %s: %s", cmd, r) } return l.parseSystemctlStatus(r.Stdout), nil } @@ -473,3 +477,125 @@ func (l *base) parseSystemctlStatus(stdout string) string { } return ss[1] } + +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 { + return nil + } + + if hasEmptyOpt { + return xerrors.Errorf("%s has empty WordPress opts: %s", + l.getServerInfo().GetServerName(), wpOpts) + } + + cmd := fmt.Sprintf("sudo -u %s -i -- %s cli version", + 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) + } + + wp, err := l.detectWordPress() + if err != nil { + return xerrors.Errorf("Failed to scan wordpress: %w", err) + } + l.WordPress = wp + return nil +} + +func (l *base) detectWordPress() (*models.WordPressPackages, error) { + ver, err := l.detectWpCore() + if err != nil { + return nil, err + } + + themes, err := l.detectWpThemes() + if err != nil { + return nil, err + } + + plugins, err := l.detectWpPlugins() + if err != nil { + return nil, err + } + + pkgs := models.WordPressPackages{ + models.WpPackage{ + Name: models.WPCore, + Version: ver, + Type: models.WPCore, + }, + } + pkgs = append(pkgs, themes...) + pkgs = append(pkgs, plugins...) + return &pkgs, nil +} + +func (l *base) detectWpCore() (string, error) { + cmd := fmt.Sprintf("sudo -u %s -i -- %s core version --path=%s", + l.ServerInfo.WordPress.OSUser, + l.ServerInfo.WordPress.CmdPath, + l.ServerInfo.WordPress.DocRoot) + + r := exec(l.ServerInfo, cmd, noSudo) + if !r.isSuccess() { + return "", xerrors.Errorf("Failed to get wp core version: %s", r) + } + return strings.TrimSpace(r.Stdout), nil +} + +func (l *base) detectWpThemes() ([]models.WpPackage, error) { + cmd := fmt.Sprintf("sudo -u %s -i -- %s theme list --path=%s --format=json", + l.ServerInfo.WordPress.OSUser, + l.ServerInfo.WordPress.CmdPath, + l.ServerInfo.WordPress.DocRoot) + + var themes []models.WpPackage + r := exec(l.ServerInfo, cmd, noSudo) + if !r.isSuccess() { + return nil, xerrors.Errorf("Failed to get a list of WordPress plugins: %s", r) + } + err := json.Unmarshal([]byte(r.Stdout), &themes) + if err != nil { + return nil, xerrors.Errorf("Failed to unmarshal wp theme list: %w", cmd, err) + } + for i := range themes { + themes[i].Type = models.WPTheme + } + return themes, nil +} + +func (l *base) detectWpPlugins() ([]models.WpPackage, error) { + cmd := fmt.Sprintf("sudo -u %s -i -- %s plugin list --path=%s --format=json", + l.ServerInfo.WordPress.OSUser, + l.ServerInfo.WordPress.CmdPath, + l.ServerInfo.WordPress.DocRoot) + + var plugins []models.WpPackage + r := exec(l.ServerInfo, cmd, noSudo) + if !r.isSuccess() { + return nil, xerrors.Errorf("Failed to wp plugin list: %s", r) + } + if err := json.Unmarshal([]byte(r.Stdout), &plugins); err != nil { + return nil, err + } + for i := range plugins { + plugins[i].Type = models.WPPlugin + } + return plugins, nil +} diff --git a/scan/debian.go b/scan/debian.go index 4e59d7d5..61812609 100644 --- a/scan/debian.go +++ b/scan/debian.go @@ -30,6 +30,7 @@ import ( "github.com/future-architect/vuls/models" "github.com/future-architect/vuls/util" version "github.com/knqyf263/go-deb-version" + "golang.org/x/xerrors" ) // inherit OsTypeInterface @@ -62,7 +63,7 @@ func detectDebian(c config.ServerInfo) (itsMe bool, deb osTypeInterface, err err return false, deb, nil } if r.ExitStatus == 255 { - return false, deb, fmt.Errorf("Unable to connect via SSH. Scan with -vvv option to print SSH debugging messages and check SSH settings. If you have never SSH to the host to be scanned, SSH to the host before scanning in order to add the HostKey. %s@%s port: %s\n%s", c.User, c.Host, c.Port, r) + return false, deb, xerrors.Errorf("Unable to connect via SSH. Scan with -vvv option to print SSH debugging messages and check SSH settings. If you have never SSH to the host to be scanned, SSH to the host before scanning in order to add the HostKey. %s@%s port: %s\n%s", c.User, c.Host, c.Port, r) } util.Log.Debugf("Not Debian like Linux. %s", r) return false, deb, nil @@ -160,7 +161,7 @@ func (o *debian) checkIfSudoNoPasswd() error { r := o.exec(cmd, sudo) if !r.isSuccess() { o.log.Errorf("sudo error on %s", r) - return fmt.Errorf("Failed to sudo: %s", r) + return xerrors.Errorf("Failed to sudo: %s", r) } } @@ -171,7 +172,7 @@ func (o *debian) checkIfSudoNoPasswd() error { r := o.exec(cmd, sudo) if !r.isSuccess() { o.log.Errorf("sudo error on %s", r) - return fmt.Errorf("Failed to sudo: %s", r) + return xerrors.Errorf("Failed to sudo: %s", r) } } @@ -230,7 +231,7 @@ func (o *debian) checkDeps() error { } dep.logFunc(msg) if dep.required { - return fmt.Errorf(msg) + return xerrors.New(msg) } continue } @@ -242,7 +243,7 @@ func (o *debian) checkDeps() error { } dep.logFunc(msg) if dep.required { - return fmt.Errorf(msg) + return xerrors.New(msg) } } @@ -323,7 +324,7 @@ func (o *debian) rebootRequired() (bool, error) { case 1: return false, nil default: - return false, fmt.Errorf("Failed to check reboot reauired: %s", r) + return false, xerrors.Errorf("Failed to check reboot reauired: %s", r) } } @@ -333,7 +334,7 @@ func (o *debian) scanInstalledPackages() (models.Packages, models.Packages, mode updatable := models.Packages{} r := o.exec(dpkgQuery, noSudo) if !r.isSuccess() { - return nil, nil, nil, fmt.Errorf("Failed to SSH: %s", r) + return nil, nil, nil, xerrors.Errorf("Failed to SSH: %s", r) } installed, srcPacks, err := o.parseInstalledPackages(r.Stdout) @@ -364,7 +365,7 @@ func (o *debian) scanInstalledPackages() (models.Packages, models.Packages, mode // Fill the candidate versions of upgradable packages err = o.fillCandidateVersion(updatable) if err != nil { - return nil, nil, nil, fmt.Errorf("Failed to fill candidate versions. err: %s", err) + return nil, nil, nil, xerrors.Errorf("Failed to fill candidate versions. err: %s", err) } installed.MergeNewVersion(updatable) @@ -383,7 +384,7 @@ func (o *debian) parseInstalledPackages(stdout string) (models.Packages, models. if trimmed := strings.TrimSpace(line); len(trimmed) != 0 { name, status, version, srcName, srcVersion, err := o.parseScannedPackagesLine(trimmed) if err != nil || len(status) < 2 { - return nil, nil, fmt.Errorf( + return nil, nil, xerrors.Errorf( "Debian: Failed to parse package line: %s", line) } @@ -448,14 +449,14 @@ func (o *debian) parseScannedPackagesLine(line string) (name, status, version, s return } - return "", "", "", "", "", fmt.Errorf("Unknown format: %s", line) + return "", "", "", "", "", xerrors.Errorf("Unknown format: %s", line) } func (o *debian) aptGetUpdate() error { o.log.Infof("apt-get update...") cmd := util.PrependProxyEnv("apt-get update") if r := o.exec(cmd, sudo); !r.isSuccess() { - return fmt.Errorf("Failed to apt-get update: %s", r) + return xerrors.Errorf("Failed to apt-get update: %s", r) } return nil } @@ -477,7 +478,7 @@ func (o *debian) scanUnsecurePackages(updatable models.Packages) (models.VulnInf // Collect CVE information of upgradable packages vulnInfos, err := o.scanChangelogs(updatable, meta) if err != nil { - return nil, fmt.Errorf("Failed to scan unsecure packages. err: %s", err) + return nil, xerrors.Errorf("Failed to scan unsecure packages. err: %s", err) } return vulnInfos, nil @@ -487,7 +488,7 @@ func (o *debian) ensureChangelogCache(current cache.Meta) (*cache.Meta, error) { // Search from cache cached, found, err := cache.DB.GetMeta(current.Name) if err != nil { - return nil, fmt.Errorf( + return nil, xerrors.Errorf( "Failed to get meta. Please remove cache.db and then try again. err: %s", err) } @@ -495,7 +496,7 @@ func (o *debian) ensureChangelogCache(current cache.Meta) (*cache.Meta, error) { o.log.Debugf("Not found in meta: %s", current.Name) err = cache.DB.EnsureBuckets(current) if err != nil { - return nil, fmt.Errorf("Failed to ensure buckets. err: %s", err) + return nil, xerrors.Errorf("Failed to ensure buckets. err: %s", err) } return ¤t, nil } @@ -505,7 +506,7 @@ func (o *debian) ensureChangelogCache(current cache.Meta) (*cache.Meta, error) { o.log.Debugf("Need to refesh meta: %s", current.Name) err = cache.DB.EnsureBuckets(current) if err != nil { - return nil, fmt.Errorf("Failed to ensure buckets. err: %s", err) + return nil, xerrors.Errorf("Failed to ensure buckets. err: %s", err) } return ¤t, nil @@ -526,17 +527,17 @@ func (o *debian) fillCandidateVersion(updatables models.Packages) (err error) { cmd := fmt.Sprintf("LANGUAGE=en_US.UTF-8 apt-cache policy %s", strings.Join(names, " ")) r := o.exec(cmd, noSudo) if !r.isSuccess() { - return fmt.Errorf("Failed to SSH: %s", r) + return xerrors.Errorf("Failed to SSH: %s", r) } packAptPolicy := o.splitAptCachePolicy(r.Stdout) for k, v := range packAptPolicy { ver, err := o.parseAptCachePolicy(v, k) if err != nil { - return fmt.Errorf("Failed to parse %s", err) + return xerrors.Errorf("Failed to parse %w", err) } pack, ok := updatables[k] if !ok { - return fmt.Errorf("Not found: %s", k) + return xerrors.Errorf("Not found: %s", k) } pack.NewVersion = ver.Candidate pack.Repository = ver.Repo @@ -551,7 +552,7 @@ func (o *debian) getUpdatablePackNames() (packNames []string, err error) { if r.isSuccess(0, 1) { return o.parseAptGetUpgrade(r.Stdout) } - return packNames, fmt.Errorf( + return packNames, xerrors.Errorf( "Failed to %s. status: %d, stdout: %s, stderr: %s", cmd, r.ExitStatus, r.Stdout, r.Stderr) } @@ -573,11 +574,11 @@ func (o *debian) parseAptGetUpgrade(stdout string) (updatableNames []string, err if len(result) == 2 { nUpdatable, err := strconv.Atoi(result[1]) if err != nil { - return nil, fmt.Errorf( + return nil, xerrors.Errorf( "Failed to scan upgradable packages number. line: %s", line) } if nUpdatable != len(updatableNames) { - return nil, fmt.Errorf( + return nil, xerrors.Errorf( "Failed to scan upgradable packages, expected: %s, detected: %d", result[1], len(updatableNames)) } @@ -593,7 +594,7 @@ func (o *debian) parseAptGetUpgrade(stdout string) (updatableNames []string, err } if !stopLineFound { // There are upgrades, but not found the stop line. - return nil, fmt.Errorf("Failed to scan upgradable packages") + return nil, xerrors.New("Failed to scan upgradable packages") } return } @@ -677,11 +678,11 @@ func (o *debian) scanChangelogs(updatablePacks models.Packages, meta *cache.Meta case err := <-errChan: errs = append(errs, err) case <-timeout: - errs = append(errs, fmt.Errorf("Timeout scanPackageCveIDs")) + errs = append(errs, xerrors.New("Timeout scanPackageCveIDs")) } } if 0 < len(errs) { - return nil, fmt.Errorf("%v", errs) + return nil, xerrors.Errorf("errs: %w", errs) } var cveIDs []DetectedCveID @@ -691,9 +692,9 @@ func (o *debian) scanChangelogs(updatablePacks models.Packages, meta *cache.Meta o.log.Debugf("%d Cves are found. cves: %v", len(cveIDs), cveIDs) vinfos := models.VulnInfos{} for cveID, names := range cvePackages { - affected := models.PackageStatuses{} + affected := models.PackageFixStatuses{} for _, n := range names { - affected = append(affected, models.PackageStatus{Name: n}) + affected = append(affected, models.PackageFixStatus{Name: n}) } vinfos[cveID.CveID] = models.VulnInfo{ @@ -764,7 +765,7 @@ func (o *debian) fetchParseChangelog(pack models.Package) ([]DetectedCveID, *mod err := cache.DB.PutChangelog( o.getServerInfo().GetServerName(), pack.Name, pack.Changelog.Contents) if err != nil { - return nil, nil, fmt.Errorf("Failed to put changelog into cache") + return nil, nil, xerrors.New("Failed to put changelog into cache") } } @@ -838,7 +839,7 @@ var cveRe = regexp.MustCompile(`(CVE-\d{4}-\d{4,})`) func (o *debian) parseChangelog(changelog, name, ver string, confidence models.Confidence) ([]DetectedCveID, *models.Package, error) { installedVer, err := version.NewVersion(ver) if err != nil { - return nil, nil, fmt.Errorf("Failed to parse installed version: %s, %s", ver, err) + return nil, nil, xerrors.Errorf("Failed to parse installed version: %s, err: %w", ver, err) } buf, cveIDs := []string{}, []string{} scanner := bufio.NewScanner(strings.NewReader(changelog)) @@ -876,7 +877,7 @@ func (o *debian) parseChangelog(changelog, name, ver string, confidence models.C Contents: "", Method: models.FailedToFindVersionInChangelog, } - return nil, &pack, fmt.Errorf( + return nil, &pack, xerrors.Errorf( "Failed to scan CVE IDs. The version is not in changelog. name: %s, version: %s", name, ver) } @@ -958,7 +959,7 @@ func (o *debian) parseAptCachePolicy(stdout, name string) (packCandidateVer, err } nextline: } - return ver, fmt.Errorf("Unknown Format: %s", stdout) + return ver, xerrors.Errorf("Unknown Format: %s", stdout) } func (o *debian) checkrestart() error { @@ -971,7 +972,7 @@ func (o *debian) checkrestart() error { cmd := "LANGUAGE=en_US.UTF-8 checkrestart" r := o.exec(cmd, sudo) if !r.isSuccess() { - return fmt.Errorf( + return xerrors.Errorf( "Failed to %s. status: %d, stdout: %s, stderr: %s", cmd, r.ExitStatus, r.Stdout, r.Stderr) } diff --git a/scan/executil.go b/scan/executil.go index 969d4684..ad7811fd 100644 --- a/scan/executil.go +++ b/scan/executil.go @@ -33,6 +33,7 @@ import ( "golang.org/x/crypto/ssh" "golang.org/x/crypto/ssh/agent" + "golang.org/x/xerrors" "github.com/cenkalti/backoff" conf "github.com/future-architect/vuls/config" @@ -124,7 +125,11 @@ func parallelExec(fn func(osTypeInterface) error, timeoutSec ...int) { if len(s.getErrs()) == 0 { successes = append(successes, s) } else { - util.Log.Errorf("Error: %s, err: %s", + fmtstr := "Error on %s, err: %s" + if conf.Conf.Debug { + fmtstr = "Error: %s, err: %+v" + } + util.Log.Errorf(fmtstr, s.getServerInfo().GetServerName(), s.getErrs()) errServers = append(errServers, s) } @@ -148,7 +153,7 @@ func parallelExec(fn func(osTypeInterface) error, timeoutSec ...int) { msg := fmt.Sprintf("Timed out: %s", s.getServerInfo().GetServerName()) util.Log.Errorf(msg) - s.setErrs([]error{fmt.Errorf(msg)}) + s.setErrs([]error{xerrors.New(msg)}) errServers = append(errServers, s) } } @@ -222,8 +227,8 @@ func sshExecNative(c conf.ServerInfo, cmd string, sudo bool) (result execResult) var session *ssh.Session if session, err = client.NewSession(); err != nil { - result.Error = fmt.Errorf( - "Failed to create a new session. servername: %s, err: %s", + result.Error = xerrors.Errorf( + "Failed to create a new session. servername: %s, err: %w", c.ServerName, err) result.ExitStatus = 999 return @@ -237,8 +242,8 @@ func sshExecNative(c conf.ServerInfo, cmd string, sudo bool) (result execResult) ssh.TTY_OP_OSPEED: 14400, // output speed = 14.4kbaud } if err = session.RequestPty("xterm", 400, 1000, modes); err != nil { - result.Error = fmt.Errorf( - "Failed to request for pseudo terminal. servername: %s, err: %s", + result.Error = xerrors.Errorf( + "Failed to request for pseudo terminal. servername: %s, err: %w", c.ServerName, err) result.ExitStatus = 999 return @@ -462,7 +467,7 @@ func addKeyAuth(auths []ssh.AuthMethod, keypath string, keypassword string) ([]s // get first pem block block, _ := pem.Decode(pemBytes) if block == nil { - return auths, fmt.Errorf("no key found in %s", keypath) + return auths, xerrors.Errorf("no key found in %s", keypath) } // handle plain and encrypted keyfiles @@ -499,6 +504,6 @@ func parsePemBlock(block *pem.Block) (interface{}, error) { case "DSA PRIVATE KEY": return ssh.ParseDSAPrivateKey(block.Bytes) default: - return nil, fmt.Errorf("Unsupported key type %q", block.Type) + return nil, xerrors.Errorf("Unsupported key type %q", block.Type) } } diff --git a/scan/freebsd.go b/scan/freebsd.go index 4267bbbb..a055a6ff 100644 --- a/scan/freebsd.go +++ b/scan/freebsd.go @@ -18,13 +18,13 @@ along with this program. If not, see . package scan import ( - "fmt" "net" "strings" "github.com/future-architect/vuls/config" "github.com/future-architect/vuls/models" "github.com/future-architect/vuls/util" + "golang.org/x/xerrors" ) // inherit OsTypeInterface @@ -69,7 +69,7 @@ func detectFreebsd(c config.ServerInfo) (itsMe bool, bsd osTypeInterface) { func (o *bsd) checkScanMode() error { if o.getServerInfo().Mode.IsOffline() { - return fmt.Errorf("Remove offline scan mode, FreeBSD needs internet connection") + return xerrors.New("Remove offline scan mode, FreeBSD needs internet connection") } return nil } @@ -101,7 +101,7 @@ func (o *bsd) postScan() error { func (o *bsd) detectIPAddr() (err error) { r := o.exec("/sbin/ifconfig", noSudo) if !r.isSuccess() { - return fmt.Errorf("Failed to detect IP address: %v", r) + return xerrors.Errorf("Failed to detect IP address: %v", r) } o.ServerInfo.IPv4Addrs, o.ServerInfo.IPv6Addrs = o.parseIfconfig(r.Stdout) return nil @@ -173,7 +173,7 @@ func (o *bsd) parseInstalledPackages(string) (models.Packages, models.SrcPackage func (o *bsd) rebootRequired() (bool, error) { r := o.exec("freebsd-version -k", noSudo) if !r.isSuccess() { - return false, fmt.Errorf("Failed to SSH: %s", r) + return false, xerrors.Errorf("Failed to SSH: %s", r) } return o.Kernel.Release != strings.TrimSpace(r.Stdout), nil } @@ -182,7 +182,7 @@ func (o *bsd) scanInstalledPackages() (models.Packages, error) { cmd := util.PrependProxyEnv("pkg version -v") r := o.exec(cmd, noSudo) if !r.isSuccess() { - return nil, fmt.Errorf("Failed to SSH: %s", r) + return nil, xerrors.Errorf("Failed to SSH: %s", r) } return o.parsePkgVersion(r.Stdout), nil } @@ -192,13 +192,13 @@ func (o *bsd) scanUnsecurePackages() (models.VulnInfos, error) { cmd := "rm -f " + vulndbPath r := o.exec(cmd, noSudo) if !r.isSuccess(0) { - return nil, fmt.Errorf("Failed to SSH: %s", r) + return nil, xerrors.Errorf("Failed to SSH: %s", r) } cmd = util.PrependProxyEnv("pkg audit -F -r -f " + vulndbPath) r = o.exec(cmd, noSudo) if !r.isSuccess(0, 1) { - return nil, fmt.Errorf("Failed to SSH: %s", r) + return nil, xerrors.Errorf("Failed to SSH: %s", r) } if r.ExitStatus == 0 { // no vulnerabilities @@ -214,7 +214,7 @@ func (o *bsd) scanUnsecurePackages() (models.VulnInfos, error) { } pack, found := o.Packages[name] if !found { - return nil, fmt.Errorf("Vulnerable package: %s is not found", name) + return nil, xerrors.Errorf("Vulnerable package: %s is not found", name) } packAdtRslt = append(packAdtRslt, pkgAuditResult{ pack: pack, @@ -247,9 +247,9 @@ func (o *bsd) scanUnsecurePackages() (models.VulnInfos, error) { }) } - affected := models.PackageStatuses{} + affected := models.PackageFixStatuses{} for name := range packs { - affected = append(affected, models.PackageStatus{ + affected = append(affected, models.PackageFixStatus{ Name: name, }) } diff --git a/scan/redhatbase.go b/scan/redhatbase.go index b0fbec28..4cef57d3 100644 --- a/scan/redhatbase.go +++ b/scan/redhatbase.go @@ -27,6 +27,7 @@ import ( "github.com/future-architect/vuls/config" "github.com/future-architect/vuls/models" "github.com/future-architect/vuls/util" + "golang.org/x/xerrors" ver "github.com/knqyf263/go-rpm-version" ) @@ -160,7 +161,7 @@ func (o *redhatBase) execCheckIfSudoNoPasswd(cmds []cmd) error { r := o.exec(util.PrependProxyEnv(cmd), sudo) if !r.isSuccess(c.expectedStatusCodes...) { o.log.Errorf("Check sudo or proxy settings: %s", r) - return fmt.Errorf("Failed to sudo: %s", r) + return xerrors.Errorf("Failed to sudo: %s", r) } } o.log.Infof("Sudo... Pass") @@ -173,7 +174,7 @@ func (o *redhatBase) execCheckDeps(packNames []string) error { if r := o.exec(cmd, noSudo); !r.isSuccess() { msg := fmt.Sprintf("%s is not installed", name) o.log.Errorf(msg) - return fmt.Errorf(msg) + return xerrors.New(msg) } } o.log.Infof("Dependencies ... Pass") @@ -191,12 +192,12 @@ func (o *redhatBase) preCure() error { func (o *redhatBase) postScan() error { if o.isExecYumPS() { if err := o.yumPS(); err != nil { - return fmt.Errorf("Failed to execute yum-ps: %s", err) + return xerrors.Errorf("Failed to execute yum-ps: %w", err) } } if o.isExecNeedsRestarting() { if err := o.needsRestarting(); err != nil { - return fmt.Errorf("Failed to execute need-restarting: %s", err) + return xerrors.Errorf("Failed to execute need-restarting: %w", err) } } return nil @@ -258,7 +259,7 @@ func (o *redhatBase) rebootRequired() (bool, error) { r := o.exec("rpm -q --last kernel", noSudo) scanner := bufio.NewScanner(strings.NewReader(r.Stdout)) if !r.isSuccess(0, 1) { - return false, fmt.Errorf("Failed to detect the last installed kernel : %v", r) + return false, xerrors.Errorf("Failed to detect the last installed kernel : %v", r) } if !r.isSuccess() || !scanner.Scan() { return false, nil @@ -280,7 +281,7 @@ func (o *redhatBase) scanInstalledPackages() (models.Packages, error) { r := o.exec(rpmQa(o.Distro), noSudo) if !r.isSuccess() { - return nil, fmt.Errorf("Scan packages failed: %s", r) + return nil, xerrors.Errorf("Scan packages failed: %s", r) } installed, _, err := o.parseInstalledPackages(r.Stdout) if err != nil { @@ -332,7 +333,7 @@ func (o *redhatBase) parseInstalledPackagesLine(line string) (models.Package, er fields := strings.Fields(line) if len(fields) != 5 { return models.Package{}, - fmt.Errorf("Failed to parse package line: %s", line) + xerrors.Errorf("Failed to parse package line: %s", line) } ver := "" epoch := fields[1] @@ -358,7 +359,7 @@ func (o *redhatBase) scanUpdatablePackages() (models.Packages, error) { r := o.exec(util.PrependProxyEnv(cmd), o.sudo.repoquery()) if !r.isSuccess() { - return nil, fmt.Errorf("Failed to SSH: %s", r) + return nil, xerrors.Errorf("Failed to SSH: %s", r) } // Collect Updateble packages, installed, candidate version and repository. @@ -393,7 +394,7 @@ func (o *redhatBase) parseUpdatablePacksLines(stdout string) (models.Packages, e func (o *redhatBase) parseUpdatablePacksLine(line string) (models.Package, error) { fields := strings.Fields(line) if len(fields) < 5 { - return models.Package{}, fmt.Errorf("Unknown format: %s, fields: %s", line, fields) + return models.Package{}, xerrors.Errorf("Unknown format: %s, fields: %s", line, fields) } ver := "" @@ -562,7 +563,7 @@ func (o *redhatBase) getAvailableChangelogs(packNames []string) (map[string]stri r := o.exec(util.PrependProxyEnv(cmd), o.sudo.yumChangelog()) if !r.isSuccess(0, 1) { - return nil, fmt.Errorf("Failed to SSH: %s", r) + return nil, xerrors.Errorf("Failed to SSH: %s", r) } return o.divideChangelogsIntoEachPackages(r.Stdout), nil @@ -723,7 +724,7 @@ func (o *redhatBase) getDiffChangelog(pack models.Package, availableChangelog st if len(diff) == 0 || !found { return availableChangelog, - fmt.Errorf("Failed to find the version in changelog: %s-%s-%s", + xerrors.Errorf("Failed to find the version in changelog: %s-%s-%s", pack.Name, pack.Version, pack.Release) } return strings.TrimSpace(strings.Join(diff, "\n")), nil @@ -760,12 +761,12 @@ func (o *redhatBase) scanChangelogs(updatable models.Packages) (models.VulnInfos for name, cveIDs := range packCveIDs { for _, cid := range cveIDs { if v, ok := vinfos[cid]; ok { - v.AffectedPackages = append(v.AffectedPackages, models.PackageStatus{Name: name}) + v.AffectedPackages = append(v.AffectedPackages, models.PackageFixStatus{Name: name}) vinfos[cid] = v } else { vinfos[cid] = models.VulnInfo{ CveID: cid, - AffectedPackages: models.PackageStatuses{{Name: name}}, + AffectedPackages: models.PackageFixStatuses{{Name: name}}, Confidences: models.Confidences{models.ChangelogExactMatch}, } } @@ -784,14 +785,14 @@ type distroAdvisoryCveIDs struct { func (o *redhatBase) scanUsingYum(updatable models.Packages) (models.VulnInfos, error) { if o.Distro.Family == config.CentOS { // CentOS has no security channel. - return nil, fmt.Errorf( + return nil, xerrors.New( "yum updateinfo is not suppported on CentOS") } // get advisoryID(RHSA, ALAS, ELSA) - package name,version major, err := (o.Distro.MajorVersion()) if err != nil { - return nil, fmt.Errorf("Not implemented yet: %s, err: %s", o.Distro, err) + return nil, xerrors.Errorf("Not implemented yet: %s, err: %w", o.Distro, err) } var cmd string @@ -799,7 +800,7 @@ func (o *redhatBase) scanUsingYum(updatable models.Packages) (models.VulnInfos, cmd = "yum repolist --color=never" r := o.exec(util.PrependProxyEnv(cmd), o.sudo.yumRepolist()) if !r.isSuccess() { - return nil, fmt.Errorf("Failed to SSH: %s", r) + return nil, xerrors.Errorf("Failed to SSH: %s", r) } } @@ -813,7 +814,7 @@ func (o *redhatBase) scanUsingYum(updatable models.Packages) (models.VulnInfos, } r := o.exec(util.PrependProxyEnv(cmd), o.sudo.yumUpdateInfo()) if !r.isSuccess() { - return nil, fmt.Errorf("Failed to SSH: %s", r) + return nil, xerrors.Errorf("Failed to SSH: %s", r) } advIDPackNamesList, err := o.parseYumUpdateinfoListAvailable(r.Stdout) @@ -823,7 +824,7 @@ func (o *redhatBase) scanUsingYum(updatable models.Packages) (models.VulnInfos, for _, packName := range advIDPackNames.PackNames { pack, found := updatable[packName] if !found { - return nil, fmt.Errorf( + return nil, xerrors.Errorf( "Package not found. pack: %#v", packName) } packages[pack.Name] = pack @@ -843,7 +844,7 @@ func (o *redhatBase) scanUsingYum(updatable models.Packages) (models.VulnInfos, } r = o.exec(util.PrependProxyEnv(cmd), o.sudo.yumUpdateInfo()) if !r.isSuccess() { - return nil, fmt.Errorf("Failed to SSH: %s", r) + return nil, xerrors.Errorf("Failed to SSH: %s", r) } advisoryCveIDsList, err := o.parseYumUpdateinfo(r.Stdout) if err != nil { @@ -863,13 +864,13 @@ func (o *redhatBase) scanUsingYum(updatable models.Packages) (models.VulnInfos, packs := dict[advIDCveIDs.DistroAdvisory.AdvisoryID] for _, pack := range packs { vinfo.AffectedPackages = append(vinfo.AffectedPackages, - models.PackageStatus{Name: pack.Name}) + models.PackageFixStatus{Name: pack.Name}) } } else { packs := dict[advIDCveIDs.DistroAdvisory.AdvisoryID] - affected := models.PackageStatuses{} + affected := models.PackageFixStatuses{} for _, p := range packs { - affected = append(affected, models.PackageStatus{Name: p.Name}) + affected = append(affected, models.PackageFixStatus{Name: p.Name}) } vinfo = models.VulnInfo{ CveID: cveID, @@ -931,7 +932,7 @@ func (o *redhatBase) parseYumUpdateinfo(stdout string) (result []distroAdvisoryC switch o.Distro.Family { case config.CentOS: // CentOS has no security channel. - return result, fmt.Errorf( + return result, xerrors.New( "yum updateinfo is not suppported on CentOS") case config.RedHat, config.Amazon, config.Oracle: // nop @@ -1118,7 +1119,7 @@ func (o *redhatBase) parseYumUpdateinfoListAvailable(stdout string) (advisoryIDP fields := strings.Fields(line) if len(fields) != 3 { - return []advisoryIDPacks{}, fmt.Errorf( + return []advisoryIDPacks{}, xerrors.Errorf( "Unknown format. line: %s", line) } @@ -1151,21 +1152,21 @@ func (o *redhatBase) yumPS() error { cmd := "LANGUAGE=en_US.UTF-8 yum info yum" r := o.exec(util.PrependProxyEnv(cmd), noSudo) if !r.isSuccess() { - return fmt.Errorf("Failed to SSH: %s", r) + return xerrors.Errorf("Failed to SSH: %s", r) } if !o.checkYumPsInstalled(r.Stdout) { switch o.Distro.Family { case config.RedHat, config.Oracle: return nil default: - return fmt.Errorf("yum-plugin-ps is not installed") + return xerrors.New("yum-plugin-ps is not installed") } } cmd = "LANGUAGE=en_US.UTF-8 yum -q ps all --color=never" r = o.exec(util.PrependProxyEnv(cmd), sudo) if !r.isSuccess() { - return fmt.Errorf("Failed to SSH: %s", r) + return xerrors.Errorf("Failed to SSH: %s", r) } packs := o.parseYumPS(r.Stdout) for name, pack := range packs { @@ -1260,7 +1261,7 @@ func (o *redhatBase) needsRestarting() error { cmd := "LANGUAGE=en_US.UTF-8 needs-restarting" r := o.exec(cmd, sudo) if !r.isSuccess() { - return fmt.Errorf("Failed to SSH: %s", r) + return xerrors.Errorf("Failed to SSH: %s", r) } procs := o.parseNeedsRestarting(r.Stdout) for _, proc := range procs { @@ -1335,7 +1336,7 @@ func (o *redhatBase) procPathToFQPN(execCommand string) (string, error) { cmd := `LANGUAGE=en_US.UTF-8 rpm -qf --queryformat "%{NAME}-%{EPOCH}:%{VERSION}-%{RELEASE}.%{ARCH}\n" ` + path r := o.exec(cmd, noSudo) if !r.isSuccess() { - return "", fmt.Errorf("Failed to SSH: %s", r) + return "", xerrors.Errorf("Failed to SSH: %s", r) } fqpn := strings.TrimSpace(r.Stdout) return strings.Replace(fqpn, "-(none):", "-", -1), nil diff --git a/scan/rhel.go b/scan/rhel.go index 70f311af..ceb6142d 100644 --- a/scan/rhel.go +++ b/scan/rhel.go @@ -1,11 +1,10 @@ package scan import ( - "fmt" - "github.com/future-architect/vuls/config" "github.com/future-architect/vuls/models" "github.com/future-architect/vuls/util" + "golang.org/x/xerrors" ) // inherit OsTypeInterface @@ -43,7 +42,7 @@ func (o *rhel) checkDeps() error { } else if o.getServerInfo().Mode.IsDeep() { return o.execCheckDeps(o.depsDeep()) } - return fmt.Errorf("Unknown scan mode") + return xerrors.New("Unknown scan mode") } func (o *rhel) depsFast() []string { diff --git a/scan/serverapi.go b/scan/serverapi.go index 119bc362..cacf193c 100644 --- a/scan/serverapi.go +++ b/scan/serverapi.go @@ -18,7 +18,6 @@ along with this program. If not, see . package scan import ( - "errors" "fmt" "net/http" "os" @@ -30,13 +29,14 @@ import ( "github.com/future-architect/vuls/models" "github.com/future-architect/vuls/report" "github.com/future-architect/vuls/util" + "golang.org/x/xerrors" ) var ( - errOSFamilyHeader = errors.New("X-Vuls-OS-Family header is required") - errOSReleaseHeader = errors.New("X-Vuls-OS-Release header is required") - errKernelVersionHeader = errors.New("X-Vuls-Kernel-Version header is required") - errServerNameHeader = errors.New("X-Vuls-Server-Name header is required") + errOSFamilyHeader = xerrors.New("X-Vuls-OS-Family header is required") + errOSReleaseHeader = xerrors.New("X-Vuls-OS-Release header is required") + errKernelVersionHeader = xerrors.New("X-Vuls-Kernel-Version header is required") + errServerNameHeader = xerrors.New("X-Vuls-Server-Name header is required") ) var servers, errServers []osTypeInterface @@ -56,6 +56,7 @@ type osTypeInterface interface { preCure() error postScan() error + scanWordPress() error scanPackages() error convertToModel() models.ScanResult @@ -120,7 +121,7 @@ func detectOS(c config.ServerInfo) (osType osTypeInterface) { itsMe, osType, fatalErr = detectDebianWithRetry(c) if fatalErr != nil { osType.setErrs([]error{ - fmt.Errorf("Failed to detect OS: %s", fatalErr)}) + xerrors.Errorf("Failed to detect OS: %w", fatalErr)}) return } @@ -150,7 +151,7 @@ func detectOS(c config.ServerInfo) (osType osTypeInterface) { } //TODO darwin https://github.com/mizzy/specinfra/blob/master/lib/specinfra/helper/detect_os/darwin.rb - osType.setErrs([]error{fmt.Errorf("Unknown OS Type")}) + osType.setErrs([]error{xerrors.New("Unknown OS Type")}) return } @@ -179,7 +180,7 @@ func PrintSSHableServerNames() bool { func InitServers(timeoutSec int) error { servers, errServers = detectServerOSes(timeoutSec) if len(servers) == 0 { - return fmt.Errorf("No scannable servers") + return xerrors.New("No scannable servers") } actives, inactives := detectContainerOSes(timeoutSec) @@ -240,7 +241,7 @@ func detectServerOSes(timeoutSec int) (servers, errServers []osTypeInterface) { u := &unknown{} u.setServerInfo(sInfo) u.setErrs([]error{ - fmt.Errorf("Timed out"), + xerrors.New("Timed out"), }) errServers = append(errServers, u) util.Log.Errorf("(%d/%d) Timed out: %s", @@ -300,7 +301,7 @@ func detectContainerOSes(timeoutSec int) (actives, inactives []osTypeInterface) u := &unknown{} u.setServerInfo(sInfo) u.setErrs([]error{ - fmt.Errorf("Timed out"), + xerrors.New("Timed out"), }) inactives = append(inactives) util.Log.Errorf("Timed out: %s", servername) @@ -319,8 +320,8 @@ func detectContainerOSesOnServer(containerHost osTypeInterface) (oses []osTypeIn running, err := containerHost.runningContainers() if err != nil { - containerHost.setErrs([]error{fmt.Errorf( - "Failed to get running containers on %s. err: %s", + containerHost.setErrs([]error{xerrors.Errorf( + "Failed to get running containers on %s. err: %w", containerHost.getServerInfo().ServerName, err)}) return append(oses, containerHost) } @@ -351,8 +352,8 @@ func detectContainerOSesOnServer(containerHost osTypeInterface) (oses []osTypeIn exitedContainers, err := containerHost.exitedContainers() if err != nil { - containerHost.setErrs([]error{fmt.Errorf( - "Failed to get exited containers on %s. err: %s", + containerHost.setErrs([]error{xerrors.Errorf( + "Failed to get exited containers on %s. err: %w", containerHost.getServerInfo().ServerName, err)}) return append(oses, containerHost) } @@ -386,7 +387,7 @@ func detectContainerOSesOnServer(containerHost osTypeInterface) (oses []osTypeIn } } if 0 < len(exited) || 0 < len(unknown) { - containerHost.setErrs([]error{fmt.Errorf( + containerHost.setErrs([]error{xerrors.Errorf( "Some containers on %s are exited or unknown. exited: %s, unknown: %s", containerHost.getServerInfo().ServerName, exited, unknown)}) return append(oses, containerHost) @@ -398,7 +399,7 @@ func detectContainerOSesOnServer(containerHost osTypeInterface) (oses []osTypeIn func CheckScanModes() error { for _, s := range servers { if err := s.checkScanMode(); err != nil { - return fmt.Errorf("servers.%s.scanMode err: %s", + return xerrors.Errorf("servers.%s.scanMode err: %w", s.getServerInfo().GetServerName(), err) } } @@ -456,7 +457,7 @@ func detectPlatforms(timeoutSec int) { // Scan scan func Scan(timeoutSec int) error { if len(servers) == 0 { - return fmt.Errorf("No server defined. Check the configuration") + return xerrors.New("No server defined. Check the configuration") } if err := setupChangelogCache(); err != nil { @@ -534,7 +535,7 @@ func ViaHTTP(header http.Header, body string) (models.ScanResult, error) { redhatBase: redhatBase{base: base}, } default: - return models.ScanResult{}, fmt.Errorf("Server mode for %s is not implemented yet", family) + return models.ScanResult{}, xerrors.Errorf("Server mode for %s is not implemented yet", family) } installedPackages, srcPackages, err := osType.parseInstalledPackages(body) @@ -590,6 +591,9 @@ func scanVulns(jsonDir string, scannedAt time.Time, timeoutSec int) error { if err = o.scanPackages(); err != nil { return err } + if err = o.scanWordPress(); err != nil { + return xerrors.Errorf("Failed to scan WordPress: %w", err) + } return o.postScan() }, timeoutSec) @@ -617,7 +621,7 @@ func scanVulns(jsonDir string, scannedAt time.Time, timeoutSec int) error { } for _, w := range ws { if err := w.Write(results...); err != nil { - return fmt.Errorf("Failed to write summary report: %s", err) + return xerrors.Errorf("Failed to write summary report: %s", err) } } @@ -636,20 +640,20 @@ func EnsureResultDir(scannedAt time.Time) (currentDir string, err error) { } jsonDir := filepath.Join(resultsDir, jsonDirName) if err := os.MkdirAll(jsonDir, 0700); err != nil { - return "", fmt.Errorf("Failed to create dir: %s", err) + return "", xerrors.Errorf("Failed to create dir: %w", err) } symlinkPath := filepath.Join(resultsDir, "current") if _, err := os.Lstat(symlinkPath); err == nil { if err := os.Remove(symlinkPath); err != nil { - return "", fmt.Errorf( - "Failed to remove symlink. path: %s, err: %s", symlinkPath, err) + return "", xerrors.Errorf( + "Failed to remove symlink. path: %s, err: %w", symlinkPath, err) } } if err := os.Symlink(jsonDir, symlinkPath); err != nil { - return "", fmt.Errorf( - "Failed to create symlink: path: %s, err: %s", symlinkPath, err) + return "", xerrors.Errorf( + "Failed to create symlink: path: %s, err: %w", symlinkPath, err) } return jsonDir, nil } diff --git a/scan/suse.go b/scan/suse.go index 48dbab8c..1ac735e6 100644 --- a/scan/suse.go +++ b/scan/suse.go @@ -9,6 +9,7 @@ import ( "github.com/future-architect/vuls/config" "github.com/future-architect/vuls/models" "github.com/future-architect/vuls/util" + "golang.org/x/xerrors" ) // inherit OsTypeInterface @@ -160,7 +161,7 @@ func (o *suse) scanUpdatablePackages() (models.Packages, error) { } r := o.exec(cmd, noSudo) if !r.isSuccess() { - return nil, fmt.Errorf("Failed to scan updatable packages: %v", r) + return nil, xerrors.Errorf("Failed to scan updatable packages: %v", r) } return o.parseZypperLULines(r.Stdout) } @@ -186,7 +187,7 @@ func (o *suse) parseZypperLULines(stdout string) (models.Packages, error) { func (o *suse) parseZypperLUOneLine(line string) (*models.Package, error) { ss := strings.Split(line, "|") if len(ss) != 6 { - return nil, fmt.Errorf("zypper -q lu Unknown format: %s", line) + return nil, xerrors.Errorf("zypper -q lu Unknown format: %s", line) } available := strings.Split(strings.TrimSpace(ss[4]), "-") return &models.Package{ diff --git a/wordpress/wordpress.go b/wordpress/wordpress.go new file mode 100644 index 00000000..8eba6c19 --- /dev/null +++ b/wordpress/wordpress.go @@ -0,0 +1,271 @@ +/* Vuls - Vulnerability Scanner +Copyright (C) 2016 Future Corporation , Japan. + +This program is free software: you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation, either version 3 of the License, or +(at your option) any later version. + +This program is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with this program. If not, see . +*/ + +package wordpress + +import ( + "encoding/json" + "fmt" + "io/ioutil" + "net/http" + "strings" + + "github.com/future-architect/vuls/models" + "github.com/future-architect/vuls/util" + version "github.com/hashicorp/go-version" + "golang.org/x/xerrors" +) + +//WpCveInfos is for wpvulndb's json +type WpCveInfos struct { + ReleaseDate string `json:"release_date"` + ChangelogURL string `json:"changelog_url"` + // Status string `json:"status"` + LatestVersion string `json:"latest_version"` + LastUpdated string `json:"last_updated"` + // Popular bool `json:"popular"` + Vulnerabilities []WpCveInfo `json:"vulnerabilities"` + Error string `json:"error"` +} + +//WpCveInfo is for wpvulndb's json +type WpCveInfo struct { + ID int `json:"id"` + Title string `json:"title"` + CreatedAt string `json:"created_at"` + UpdatedAt string `json:"updated_at"` + // PublishedDate string `json:"published_date"` + VulnType string `json:"vuln_type"` + References References `json:"references"` + FixedIn string `json:"fixed_in"` +} + +//References is for wpvulndb's json +type References struct { + URL []string `json:"url"` + Cve []string `json:"cve"` + Secunia []string `json:"secunia"` +} + +// FillWordPress access to wpvulndb and fetch scurity alerts and then set to the given ScanResult. +// https://wpvulndb.com/ +func FillWordPress(r *models.ScanResult, token string) (int, error) { + // Core + ver := strings.Replace(r.WordPressPackages.CoreVersion(), ".", "", -1) + if ver == "" { + return 0, xerrors.New("Failed to get WordPress core version") + } + url := fmt.Sprintf("https://wpvulndb.com/api/v3/wordpresses/%s", ver) + body, err := httpRequest(url, token) + if err != nil { + return 0, err + } + if body == "" { + util.Log.Warnf("A result of REST access is empty: %s", url) + } + wpVinfos, err := convertToVinfos(models.WPCore, body) + if err != nil { + return 0, err + } + + //TODO add a flag ignore inactive plugin or themes such as -wp-ignore-inactive flag to cmd line option or config.toml + + // Themes + for _, p := range r.WordPressPackages.Themes() { + url := fmt.Sprintf("https://wpvulndb.com/api/v3/themes/%s", p.Name) + body, err := httpRequest(url, token) + if err != nil { + return 0, err + } + if body == "" { + continue + } + + templateVinfos, err := convertToVinfos(p.Name, body) + if err != nil { + return 0, err + } + + for _, v := range templateVinfos { + for _, fixstat := range v.WpPackageFixStats { + pkg, ok := r.WordPressPackages.Find(fixstat.Name) + if !ok { + continue + } + ok, err := match(pkg.Version, fixstat.FixedIn) + if err != nil { + return 0, xerrors.Errorf("Not a semantic versioning: %w", err) + } + if ok { + wpVinfos = append(wpVinfos, v) + util.Log.Infof("[match] %s installed: %s, fixedIn: %s", pkg.Name, pkg.Version, fixstat.FixedIn) + } else { + //TODO Debugf + util.Log.Infof("[miss] %s installed: %s, fixedIn: %s", pkg.Name, pkg.Version, fixstat.FixedIn) + } + } + } + } + + // Plugins + for _, p := range r.WordPressPackages.Plugins() { + url := fmt.Sprintf("https://wpvulndb.com/api/v3/plugins/%s", p.Name) + body, err := httpRequest(url, token) + if err != nil { + return 0, err + } + if body == "" { + continue + } + + pluginVinfos, err := convertToVinfos(p.Name, body) + if err != nil { + return 0, err + } + + for _, v := range pluginVinfos { + for _, fixstat := range v.WpPackageFixStats { + pkg, ok := r.WordPressPackages.Find(fixstat.Name) + if !ok { + continue + } + ok, err := match(pkg.Version, fixstat.FixedIn) + if err != nil { + return 0, xerrors.Errorf("Not a semantic versioning: %w", err) + } + if ok { + wpVinfos = append(wpVinfos, v) + //TODO Debugf + util.Log.Infof("[match] %s installed: %s, fixedIn: %s", pkg.Name, pkg.Version, fixstat.FixedIn) + } else { + //TODO Debugf + util.Log.Infof("[miss] %s installed: %s, fixedIn: %s", pkg.Name, pkg.Version, fixstat.FixedIn) + } + } + } + } + + for _, wpVinfo := range wpVinfos { + if vinfo, ok := r.ScannedCves[wpVinfo.CveID]; ok { + vinfo.CveContents[models.WPVulnDB] = wpVinfo.CveContents[models.WPVulnDB] + vinfo.VulnType = wpVinfo.VulnType + vinfo.Confidences = append(vinfo.Confidences, wpVinfo.Confidences...) + vinfo.WpPackageFixStats = append(vinfo.WpPackageFixStats, wpVinfo.WpPackageFixStats...) + r.ScannedCves[wpVinfo.CveID] = vinfo + } else { + r.ScannedCves[wpVinfo.CveID] = wpVinfo + } + } + return len(wpVinfos), nil +} + +func match(installedVer, fixedIn string) (bool, error) { + v1, err := version.NewVersion(installedVer) + if err != nil { + return false, err + } + v2, err := version.NewVersion(fixedIn) + if err != nil { + return false, err + } + return v1.LessThan(v2), nil +} + +func convertToVinfos(pkgName, body string) (vinfos []models.VulnInfo, err error) { + if body == "" { + return + } + // "pkgName" : CVE Detailed data + pkgnameCves := map[string]WpCveInfos{} + if err = json.Unmarshal([]byte(body), &pkgnameCves); err != nil { + return nil, xerrors.Errorf("Failed to unmarshal %s. err: %w", body, err) + } + + for _, v := range pkgnameCves { + vs := extractToVulnInfos(pkgName, v.Vulnerabilities) + vinfos = append(vinfos, vs...) + } + return vinfos, nil +} + +func extractToVulnInfos(pkgName string, cves []WpCveInfo) (vinfos []models.VulnInfo) { + for _, vulnerability := range cves { + var cveIDs []string + + if len(vulnerability.References.Cve) == 0 { + cveIDs = append(cveIDs, fmt.Sprintf("WPVDBID-%d", vulnerability.ID)) + } + for _, cveNumber := range vulnerability.References.Cve { + cveIDs = append(cveIDs, "CVE-"+cveNumber) + } + + var refs []models.Reference + for _, url := range vulnerability.References.URL { + refs = append(refs, models.Reference{ + Link: url, + }) + } + + for _, cveID := range cveIDs { + vinfos = append(vinfos, models.VulnInfo{ + CveID: cveID, + CveContents: models.NewCveContents( + models.CveContent{ + Type: models.WPVulnDB, + CveID: cveID, + Title: vulnerability.Title, + References: refs, + }, + ), + VulnType: vulnerability.VulnType, + Confidences: []models.Confidence{ + models.WPVulnDBMatch, + }, + WpPackageFixStats: []models.WpPackageFixStatus{{ + Name: pkgName, + FixedIn: vulnerability.FixedIn, + }}, + }) + } + } + return +} + +func httpRequest(url, token string) (string, error) { + util.Log.Debugf("%s", url) + req, err := http.NewRequest("GET", url, nil) + if err != nil { + return "", err + } + req.Header.Set("Authorization", fmt.Sprintf("Token token=%s", token)) + resp, err := new(http.Client).Do(req) + if err != nil { + return "", err + } + body, err := ioutil.ReadAll(resp.Body) + if err != nil { + return "", err + } + defer resp.Body.Close() + if resp.StatusCode != 200 && resp.StatusCode != 404 { + return "", xerrors.Errorf("status: %s", resp.Status) + } else if resp.StatusCode == 404 { + // This package is not in WPVulnDB + return "", nil + } + return string(body), nil +} diff --git a/wordpress/wordpress_test.go b/wordpress/wordpress_test.go new file mode 100644 index 00000000..468d7fdd --- /dev/null +++ b/wordpress/wordpress_test.go @@ -0,0 +1 @@ +package wordpress