Compare commits

...

4 Commits

Author SHA1 Message Date
Kota Kanbe
0a440ca629 fix(saas): add saas subcmd (#1093) 2020-12-11 16:19:36 +09:00
Kota Kanbe
eff1dbf95b feat(scanner): vuls-scanner binary on release archive (#1092) 2020-12-11 11:05:48 +09:00
Kota Kanbe
9a32a94806 refactor: fix build warnings (#1090) 2020-12-11 06:45:39 +09:00
Shigechika AIKAWA
2534098509 fix(report): wpvulndb poor versioning(#1088) (#1089) 2020-12-11 05:53:41 +09:00
22 changed files with 561 additions and 409 deletions

View File

@@ -19,4 +19,4 @@ jobs:
github_token: ${{ secrets.GITHUB_TOKEN }}
git_user_name: kotakanbe
git_user_email: kotakanbe@gmail.com
go_version: 1.14.x
go_version: 1.15.6

View File

@@ -64,6 +64,7 @@ builds:
- -tags=scanner
main: ./contrib/future-vuls/cmd/main.go
binary: future-vuls
archives:
- id: vuls
@@ -77,6 +78,17 @@ archives:
- README*
- CHANGELOG.md
- id: vuls-scanner
name_template: '{{ .Binary }}_{{.Version}}_{{ .Os }}_{{ .Arch }}{{ if .Arm }}v{{ .Arm }}{{ end }}'
builds:
- vuls-scanner
format: tar.gz
files:
- LICENSE
- NOTICE
- README*
- CHANGELOG.md
- id: trivy-to-vuls
name_template: '{{ .Binary }}_{{.Version}}_{{ .Os }}_{{ .Arch }}{{ if .Arm }}v{{ .Arm }}{{ end }}'
builds:

View File

@@ -20,6 +20,7 @@ func main() {
subcommands.Register(&commands.ScanCmd{}, "scan")
subcommands.Register(&commands.HistoryCmd{}, "history")
subcommands.Register(&commands.ConfigtestCmd{}, "configtest")
subcommands.Register(&commands.SaaSCmd{}, "saas")
var v = flag.Bool("v", false, "Show version")

View File

@@ -10,7 +10,7 @@ import (
"github.com/future-architect/vuls/config"
"github.com/future-architect/vuls/models"
"github.com/future-architect/vuls/report"
"github.com/future-architect/vuls/saas"
"github.com/spf13/cobra"
)
@@ -73,7 +73,7 @@ func main() {
config.Conf.Saas.GroupID = groupID
config.Conf.Saas.Token = token
config.Conf.Saas.URL = url
if err = (report.SaasWriter{}).Write(scanResult); err != nil {
if err = (saas.Writer{}).Write(scanResult); err != nil {
fmt.Println(err)
os.Exit(1)
return

View File

@@ -23,7 +23,7 @@ type packCves struct {
cves []models.CveContent
}
func (deb Debian) Supported(major string) bool {
func (deb Debian) supported(major string) bool {
_, ok := map[string]string{
"8": "jessie",
"9": "stretch",
@@ -34,7 +34,7 @@ func (deb Debian) Supported(major string) bool {
// DetectUnfixed fills cve information that has in Gost
func (deb Debian) DetectUnfixed(driver db.DB, r *models.ScanResult, _ bool) (nCVEs int, err error) {
if !deb.Supported(major(r.Release)) {
if !deb.supported(major(r.Release)) {
// only logging
util.Log.Warnf("Debian %s is not supported yet", r.Release)
return 0, nil

View File

@@ -53,7 +53,7 @@ func TestDebian_Supported(t *testing.T) {
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
deb := Debian{}
if got := deb.Supported(tt.args.major); got != tt.want {
if got := deb.supported(tt.args.major); got != tt.want {
t.Errorf("Debian.Supported() = %v, want %v", got, tt.want)
}
})

View File

@@ -187,6 +187,7 @@ type PortStat struct {
PortReachableTo []string `json:"portReachableTo"`
}
// NewPortStat create a PortStat from ipPort str
func NewPortStat(ipPort string) (*PortStat, error) {
if ipPort == "" {
return &PortStat{}, nil

View File

@@ -505,6 +505,7 @@ func (r ScanResult) RemoveRaspbianPackFromResult() ScanResult {
return result
}
// ClearFields clears a given fields of ScanResult
func (r ScanResult) ClearFields(targetTagNames []string) ScanResult {
if len(targetTagNames) == 0 {
return r

View File

@@ -694,7 +694,7 @@ func (v VulnInfo) Cvss3CalcURL() string {
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",
links["WPVulnDB"] = fmt.Sprintf("https://wpscan.com/vulnerabilities/%s",
strings.TrimPrefix(v.CveID, "WPVDBID-"))
return links
}

View File

@@ -31,7 +31,7 @@ func TestTitles(t *testing.T) {
NvdXML: {
Type: NvdXML,
Summary: "Summary NVD",
// Severity is NIOT included in NVD
// Severity is NOT included in NVD
},
},
},
@@ -68,7 +68,7 @@ func TestTitles(t *testing.T) {
NvdXML: {
Type: NvdXML,
Summary: "Summary NVD",
// Severity is NIOT included in NVD
// Severity is NOT included in NVD
},
},
},
@@ -133,7 +133,7 @@ func TestSummaries(t *testing.T) {
NvdXML: {
Type: NvdXML,
Summary: "Summary NVD",
// Severity is NIOT included in NVD
// Severity is NOT included in NVD
},
},
},
@@ -171,7 +171,7 @@ func TestSummaries(t *testing.T) {
NvdXML: {
Type: NvdXML,
Summary: "Summary NVD",
// Severity is NIOT included in NVD
// Severity is NOT included in NVD
},
},
},
@@ -532,7 +532,7 @@ func TestMaxCvss2Scores(t *testing.T) {
Type: NvdXML,
Cvss2Score: 8.1,
Cvss2Vector: "AV:N/AC:L/Au:N/C:N/I:N/A:P",
// Severity is NIOT included in NVD
// Severity is NOT included in NVD
},
},
},
@@ -860,7 +860,7 @@ func TestFormatMaxCvssScore(t *testing.T) {
NvdXML: {
Type: NvdXML,
Cvss2Score: 8.1,
// Severity is NIOT included in NVD
// Severity is NOT included in NVD
},
},
},
@@ -922,7 +922,7 @@ func TestSortPackageStatues(t *testing.T) {
}
}
func TestStorePackageStatueses(t *testing.T) {
func TestStorePackageStatuses(t *testing.T) {
var tests = []struct {
pkgstats PackageFixStatuses
in PackageFixStatus
@@ -985,7 +985,7 @@ func TestAppendIfMissing(t *testing.T) {
}
}
func TestSortByConfiden(t *testing.T) {
func TestSortByConfident(t *testing.T) {
var tests = []struct {
in Confidences
out Confidences

View File

@@ -67,7 +67,7 @@ func (b Base) CheckIfOvalFetched(driver db.DB, osFamily, release string) (fetche
}
count := 0
if err := json.Unmarshal([]byte(body), &count); err != nil {
return false, xerrors.Errorf("Failed to Unmarshall. body: %s, err: %w", body, err)
return false, xerrors.Errorf("Failed to Unmarshal. body: %s, err: %w", body, err)
}
return 0 < count, nil
}
@@ -85,7 +85,7 @@ func (b Base) CheckIfOvalFresh(driver db.DB, osFamily, release string) (ok bool,
}
if err := json.Unmarshal([]byte(body), &lastModified); err != nil {
return false, xerrors.Errorf("Failed to Unmarshall. body: %s, err: %w", body, err)
return false, xerrors.Errorf("Failed to Unmarshal. body: %s, err: %w", body, err)
}
}

View File

@@ -18,7 +18,7 @@ func TestUpsert(t *testing.T) {
def ovalmodels.Definition
packName string
fixStat fixStat
upserted bool
upsert bool
out ovalResult
}{
//insert
@@ -32,7 +32,7 @@ func TestUpsert(t *testing.T) {
notFixedYet: true,
fixedIn: "1.0.0",
},
upserted: false,
upsert: false,
out: ovalResult{
[]defPacks{
{
@@ -85,7 +85,7 @@ func TestUpsert(t *testing.T) {
notFixedYet: false,
fixedIn: "3.0.0",
},
upserted: true,
upsert: true,
out: ovalResult{
[]defPacks{
{
@@ -119,9 +119,9 @@ func TestUpsert(t *testing.T) {
},
}
for i, tt := range tests {
upserted := tt.res.upsert(tt.def, tt.packName, tt.fixStat)
if tt.upserted != upserted {
t.Errorf("[%d]\nexpected: %t\n actual: %t\n", i, tt.upserted, upserted)
upsert := tt.res.upsert(tt.def, tt.packName, tt.fixStat)
if tt.upsert != upsert {
t.Errorf("[%d]\nexpected: %t\n actual: %t\n", i, tt.upsert, upsert)
}
if !reflect.DeepEqual(tt.out, tt.res) {
t.Errorf("[%d]\nexpected: %v\n actual: %v\n", i, tt.out, tt.res)

View File

@@ -3,19 +3,12 @@
package report
import (
"bytes"
"fmt"
"io/ioutil"
"os"
"reflect"
"regexp"
"sort"
"strings"
"time"
"github.com/future-architect/vuls/libmanager"
"github.com/BurntSushi/toml"
"github.com/future-architect/vuls/config"
c "github.com/future-architect/vuls/config"
"github.com/future-architect/vuls/contrib/owasp-dependency-check/parser"
@@ -28,7 +21,6 @@ import (
"github.com/future-architect/vuls/oval"
"github.com/future-architect/vuls/util"
"github.com/future-architect/vuls/wordpress"
"github.com/hashicorp/go-uuid"
gostdb "github.com/knqyf263/gost/db"
cvedb "github.com/kotakanbe/go-cve-dictionary/db"
cvemodels "github.com/kotakanbe/go-cve-dictionary/models"
@@ -391,6 +383,7 @@ func FillWithMetasploit(driver metasploitdb.DB, r *models.ScanResult) (nMetasplo
return msf.FillWithMetasploit(driver, r)
}
// DetectCpeURIsCves detects CVEs of given CPE-URIs
func DetectCpeURIsCves(driver cvedb.DB, r *models.ScanResult, cpeURIs []string) (nCVEs int, err error) {
if len(cpeURIs) != 0 && driver == nil && !config.Conf.CveDict.IsFetchViaHTTP() {
return 0, xerrors.Errorf("cpeURIs %s specified, but cve-dictionary DB not found. Fetch cve-dictionary before reporting. For details, see `https://github.com/kotakanbe/go-cve-dictionary#deploy-go-cve-dictionary`",
@@ -533,269 +526,3 @@ func fillCweDict(r *models.ScanResult) {
r.CweDict = dict
return
}
const reUUID = "[\\da-f]{8}-[\\da-f]{4}-[\\da-f]{4}-[\\da-f]{4}-[\\da-f]{12}"
// Scanning with the -containers-only flag at scan time, the UUID of Container Host may not be generated,
// so check it. Otherwise create a UUID of the Container Host and set it.
func getOrCreateServerUUID(r models.ScanResult, server c.ServerInfo) (serverUUID string, err error) {
if id, ok := server.UUIDs[r.ServerName]; !ok {
if serverUUID, err = uuid.GenerateUUID(); err != nil {
return "", xerrors.Errorf("Failed to generate UUID: %w", err)
}
} else {
matched, err := regexp.MatchString(reUUID, id)
if !matched || err != nil {
if serverUUID, err = uuid.GenerateUUID(); err != nil {
return "", xerrors.Errorf("Failed to generate UUID: %w", err)
}
}
}
return serverUUID, nil
}
// EnsureUUIDs generate a new UUID of the scan target server if UUID is not assigned yet.
// And then set the generated UUID to config.toml and scan results.
func EnsureUUIDs(configPath string, results models.ScanResults) (err error) {
// Sort Host->Container
sort.Slice(results, func(i, j int) bool {
if results[i].ServerName == results[j].ServerName {
return results[i].Container.ContainerID < results[j].Container.ContainerID
}
return results[i].ServerName < results[j].ServerName
})
re := regexp.MustCompile(reUUID)
for i, r := range results {
server := c.Conf.Servers[r.ServerName]
if server.UUIDs == nil {
server.UUIDs = map[string]string{}
}
name := ""
if r.IsContainer() {
name = fmt.Sprintf("%s@%s", r.Container.Name, r.ServerName)
serverUUID, err := getOrCreateServerUUID(r, server)
if err != nil {
return err
}
if serverUUID != "" {
server.UUIDs[r.ServerName] = serverUUID
}
} else {
name = r.ServerName
}
if id, ok := server.UUIDs[name]; ok {
ok := re.MatchString(id)
if !ok || err != nil {
util.Log.Warnf("UUID is invalid. Re-generate UUID %s: %s", id, err)
} else {
if r.IsContainer() {
results[i].Container.UUID = id
results[i].ServerUUID = server.UUIDs[r.ServerName]
} else {
results[i].ServerUUID = id
}
// continue if the UUID has already assigned and valid
continue
}
}
// Generate a new UUID and set to config and scan result
serverUUID, err := uuid.GenerateUUID()
if err != nil {
return err
}
server.UUIDs[name] = serverUUID
server = cleanForTOMLEncoding(server, c.Conf.Default)
c.Conf.Servers[r.ServerName] = server
if r.IsContainer() {
results[i].Container.UUID = serverUUID
results[i].ServerUUID = server.UUIDs[r.ServerName]
} else {
results[i].ServerUUID = serverUUID
}
}
for name, server := range c.Conf.Servers {
server = cleanForTOMLEncoding(server, c.Conf.Default)
c.Conf.Servers[name] = server
}
email := &c.Conf.EMail
if email.SMTPAddr == "" {
email = nil
}
slack := &c.Conf.Slack
if slack.HookURL == "" {
slack = nil
}
cveDict := &c.Conf.CveDict
ovalDict := &c.Conf.OvalDict
gost := &c.Conf.Gost
exploit := &c.Conf.Exploit
metasploit := &c.Conf.Metasploit
http := &c.Conf.HTTP
if http.URL == "" {
http = nil
}
syslog := &c.Conf.Syslog
if syslog.Host == "" {
syslog = nil
}
aws := &c.Conf.AWS
if aws.S3Bucket == "" {
aws = nil
}
azure := &c.Conf.Azure
if azure.AccountName == "" {
azure = nil
}
stride := &c.Conf.Stride
if stride.HookURL == "" {
stride = nil
}
hipChat := &c.Conf.HipChat
if hipChat.AuthToken == "" {
hipChat = nil
}
chatWork := &c.Conf.ChatWork
if chatWork.APIToken == "" {
chatWork = nil
}
saas := &c.Conf.Saas
if saas.GroupID == 0 {
saas = nil
}
c := struct {
CveDict *c.GoCveDictConf `toml:"cveDict"`
OvalDict *c.GovalDictConf `toml:"ovalDict"`
Gost *c.GostConf `toml:"gost"`
Exploit *c.ExploitConf `toml:"exploit"`
Metasploit *c.MetasploitConf `toml:"metasploit"`
Slack *c.SlackConf `toml:"slack"`
Email *c.SMTPConf `toml:"email"`
HTTP *c.HTTPConf `toml:"http"`
Syslog *c.SyslogConf `toml:"syslog"`
AWS *c.AWS `toml:"aws"`
Azure *c.Azure `toml:"azure"`
Stride *c.StrideConf `toml:"stride"`
HipChat *c.HipChatConf `toml:"hipChat"`
ChatWork *c.ChatWorkConf `toml:"chatWork"`
Saas *c.SaasConf `toml:"saas"`
Default c.ServerInfo `toml:"default"`
Servers map[string]c.ServerInfo `toml:"servers"`
}{
CveDict: cveDict,
OvalDict: ovalDict,
Gost: gost,
Exploit: exploit,
Metasploit: metasploit,
Slack: slack,
Email: email,
HTTP: http,
Syslog: syslog,
AWS: aws,
Azure: azure,
Stride: stride,
HipChat: hipChat,
ChatWork: chatWork,
Saas: saas,
Default: c.Conf.Default,
Servers: c.Conf.Servers,
}
// rename the current config.toml to config.toml.bak
info, err := os.Lstat(configPath)
if err != nil {
return xerrors.Errorf("Failed to lstat %s: %w", configPath, err)
}
realPath := configPath
if info.Mode()&os.ModeSymlink == os.ModeSymlink {
if realPath, err = os.Readlink(configPath); err != nil {
return xerrors.Errorf("Failed to Read link %s: %w", configPath, err)
}
}
if err := os.Rename(realPath, realPath+".bak"); err != nil {
return xerrors.Errorf("Failed to rename %s: %w", configPath, err)
}
var buf bytes.Buffer
if err := toml.NewEncoder(&buf).Encode(c); err != nil {
return xerrors.Errorf("Failed to encode to toml: %w", err)
}
str := strings.Replace(buf.String(), "\n [", "\n\n [", -1)
str = fmt.Sprintf("%s\n\n%s",
"# See README for details: https://vuls.io/docs/en/usage-settings.html",
str)
return ioutil.WriteFile(realPath, []byte(str), 0600)
}
func cleanForTOMLEncoding(server c.ServerInfo, def c.ServerInfo) c.ServerInfo {
if reflect.DeepEqual(server.Optional, def.Optional) {
server.Optional = nil
}
if def.User == server.User {
server.User = ""
}
if def.Host == server.Host {
server.Host = ""
}
if def.Port == server.Port {
server.Port = ""
}
if def.KeyPath == server.KeyPath {
server.KeyPath = ""
}
if reflect.DeepEqual(server.ScanMode, def.ScanMode) {
server.ScanMode = nil
}
if def.Type == server.Type {
server.Type = ""
}
if reflect.DeepEqual(server.CpeNames, def.CpeNames) {
server.CpeNames = nil
}
if def.OwaspDCXMLPath == server.OwaspDCXMLPath {
server.OwaspDCXMLPath = ""
}
if reflect.DeepEqual(server.IgnoreCves, def.IgnoreCves) {
server.IgnoreCves = nil
}
if reflect.DeepEqual(server.Enablerepo, def.Enablerepo) {
server.Enablerepo = nil
}
for k, v := range def.Optional {
if vv, ok := server.Optional[k]; ok && v == vv {
delete(server.Optional, k)
}
}
return server
}

View File

@@ -140,7 +140,7 @@ No CVE-IDs are found in updatable packages.
if strings.HasPrefix(vinfo.CveID, "CVE-") {
link = fmt.Sprintf("https://nvd.nist.gov/vuln/detail/%s", vinfo.CveID)
} else if strings.HasPrefix(vinfo.CveID, "WPVDBID-") {
link = fmt.Sprintf("https://wpvulndb.com/vulnerabilities/%s", strings.TrimPrefix(vinfo.CveID, "WPVDBID-"))
link = fmt.Sprintf("https://wpscan.com/vulnerabilities/%s", strings.TrimPrefix(vinfo.CveID, "WPVDBID-"))
}
data = append(data, []string{
@@ -401,7 +401,7 @@ func formatCsvList(r models.ScanResult, path string) error {
if strings.HasPrefix(vinfo.CveID, "CVE-") {
link = fmt.Sprintf("https://nvd.nist.gov/vuln/detail/%s", vinfo.CveID)
} else if strings.HasPrefix(vinfo.CveID, "WPVDBID-") {
link = fmt.Sprintf("https://wpvulndb.com/vulnerabilities/%s", strings.TrimPrefix(vinfo.CveID, "WPVDBID-"))
link = fmt.Sprintf("https://wpscan.com/vulnerabilities/%s", strings.TrimPrefix(vinfo.CveID, "WPVDBID-"))
}
data = append(data, []string{

View File

@@ -1,16 +1,14 @@
package report
package saas
import (
"bytes"
"encoding/json"
"fmt"
"io/ioutil"
"net/http"
"net/url"
"os"
"path"
"strings"
"time"
"github.com/aws/aws-sdk-go/aws"
"github.com/aws/aws-sdk-go/aws/credentials"
@@ -23,8 +21,8 @@ import (
"golang.org/x/xerrors"
)
// SaasWriter writes results to SaaS
type SaasWriter struct{}
// Writer writes results to SaaS
type Writer struct{}
// TempCredential : TempCredential
type TempCredential struct {
@@ -42,7 +40,7 @@ type payload struct {
}
// UploadSaas : UploadSaas
func (w SaasWriter) Write(rs ...models.ScanResult) (err error) {
func (w Writer) Write(rs ...models.ScanResult) (err error) {
// dir string, configPath string, config *c.Config
if len(rs) == 0 {
return nil
@@ -142,11 +140,3 @@ func (w SaasWriter) Write(rs ...models.ScanResult) (err error) {
util.Log.Infof("done")
return nil
}
func renameKeyNameUTC(scannedAt time.Time, uuid string, container models.Container) string {
timestr := scannedAt.UTC().Format(time.RFC3339)
if len(container.ContainerID) == 0 {
return fmt.Sprintf("%s/%s.json", timestr, uuid)
}
return fmt.Sprintf("%s/%s@%s.json", timestr, container.UUID, uuid)
}

294
saas/uuid.go Normal file
View File

@@ -0,0 +1,294 @@
package saas
import (
"bytes"
"fmt"
"io/ioutil"
"os"
"reflect"
"regexp"
"sort"
"strings"
"time"
"github.com/BurntSushi/toml"
c "github.com/future-architect/vuls/config"
"github.com/future-architect/vuls/models"
"github.com/future-architect/vuls/util"
"github.com/hashicorp/go-uuid"
"golang.org/x/xerrors"
)
func renameKeyNameUTC(scannedAt time.Time, uuid string, container models.Container) string {
timestr := scannedAt.UTC().Format(time.RFC3339)
if len(container.ContainerID) == 0 {
return fmt.Sprintf("%s/%s.json", timestr, uuid)
}
return fmt.Sprintf("%s/%s@%s.json", timestr, container.UUID, uuid)
}
const reUUID = "[\\da-f]{8}-[\\da-f]{4}-[\\da-f]{4}-[\\da-f]{4}-[\\da-f]{12}"
// Scanning with the -containers-only flag at scan time, the UUID of Container Host may not be generated,
// so check it. Otherwise create a UUID of the Container Host and set it.
func getOrCreateServerUUID(r models.ScanResult, server c.ServerInfo) (serverUUID string, err error) {
if id, ok := server.UUIDs[r.ServerName]; !ok {
if serverUUID, err = uuid.GenerateUUID(); err != nil {
return "", xerrors.Errorf("Failed to generate UUID: %w", err)
}
} else {
matched, err := regexp.MatchString(reUUID, id)
if !matched || err != nil {
if serverUUID, err = uuid.GenerateUUID(); err != nil {
return "", xerrors.Errorf("Failed to generate UUID: %w", err)
}
}
}
return serverUUID, nil
}
// EnsureUUIDs generate a new UUID of the scan target server if UUID is not assigned yet.
// And then set the generated UUID to config.toml and scan results.
func EnsureUUIDs(configPath string, results models.ScanResults) (err error) {
// Sort Host->Container
sort.Slice(results, func(i, j int) bool {
if results[i].ServerName == results[j].ServerName {
return results[i].Container.ContainerID < results[j].Container.ContainerID
}
return results[i].ServerName < results[j].ServerName
})
re := regexp.MustCompile(reUUID)
for i, r := range results {
server := c.Conf.Servers[r.ServerName]
if server.UUIDs == nil {
server.UUIDs = map[string]string{}
}
name := ""
if r.IsContainer() {
name = fmt.Sprintf("%s@%s", r.Container.Name, r.ServerName)
serverUUID, err := getOrCreateServerUUID(r, server)
if err != nil {
return err
}
if serverUUID != "" {
server.UUIDs[r.ServerName] = serverUUID
}
} else {
name = r.ServerName
}
if id, ok := server.UUIDs[name]; ok {
ok := re.MatchString(id)
if !ok || err != nil {
util.Log.Warnf("UUID is invalid. Re-generate UUID %s: %s", id, err)
} else {
if r.IsContainer() {
results[i].Container.UUID = id
results[i].ServerUUID = server.UUIDs[r.ServerName]
} else {
results[i].ServerUUID = id
}
// continue if the UUID has already assigned and valid
continue
}
}
// Generate a new UUID and set to config and scan result
serverUUID, err := uuid.GenerateUUID()
if err != nil {
return err
}
server.UUIDs[name] = serverUUID
server = cleanForTOMLEncoding(server, c.Conf.Default)
c.Conf.Servers[r.ServerName] = server
if r.IsContainer() {
results[i].Container.UUID = serverUUID
results[i].ServerUUID = server.UUIDs[r.ServerName]
} else {
results[i].ServerUUID = serverUUID
}
}
for name, server := range c.Conf.Servers {
server = cleanForTOMLEncoding(server, c.Conf.Default)
c.Conf.Servers[name] = server
}
email := &c.Conf.EMail
if email.SMTPAddr == "" {
email = nil
}
slack := &c.Conf.Slack
if slack.HookURL == "" {
slack = nil
}
cveDict := &c.Conf.CveDict
ovalDict := &c.Conf.OvalDict
gost := &c.Conf.Gost
exploit := &c.Conf.Exploit
metasploit := &c.Conf.Metasploit
http := &c.Conf.HTTP
if http.URL == "" {
http = nil
}
syslog := &c.Conf.Syslog
if syslog.Host == "" {
syslog = nil
}
aws := &c.Conf.AWS
if aws.S3Bucket == "" {
aws = nil
}
azure := &c.Conf.Azure
if azure.AccountName == "" {
azure = nil
}
stride := &c.Conf.Stride
if stride.HookURL == "" {
stride = nil
}
hipChat := &c.Conf.HipChat
if hipChat.AuthToken == "" {
hipChat = nil
}
chatWork := &c.Conf.ChatWork
if chatWork.APIToken == "" {
chatWork = nil
}
saas := &c.Conf.Saas
if saas.GroupID == 0 {
saas = nil
}
c := struct {
CveDict *c.GoCveDictConf `toml:"cveDict"`
OvalDict *c.GovalDictConf `toml:"ovalDict"`
Gost *c.GostConf `toml:"gost"`
Exploit *c.ExploitConf `toml:"exploit"`
Metasploit *c.MetasploitConf `toml:"metasploit"`
Slack *c.SlackConf `toml:"slack"`
Email *c.SMTPConf `toml:"email"`
HTTP *c.HTTPConf `toml:"http"`
Syslog *c.SyslogConf `toml:"syslog"`
AWS *c.AWS `toml:"aws"`
Azure *c.Azure `toml:"azure"`
Stride *c.StrideConf `toml:"stride"`
HipChat *c.HipChatConf `toml:"hipChat"`
ChatWork *c.ChatWorkConf `toml:"chatWork"`
Saas *c.SaasConf `toml:"saas"`
Default c.ServerInfo `toml:"default"`
Servers map[string]c.ServerInfo `toml:"servers"`
}{
CveDict: cveDict,
OvalDict: ovalDict,
Gost: gost,
Exploit: exploit,
Metasploit: metasploit,
Slack: slack,
Email: email,
HTTP: http,
Syslog: syslog,
AWS: aws,
Azure: azure,
Stride: stride,
HipChat: hipChat,
ChatWork: chatWork,
Saas: saas,
Default: c.Conf.Default,
Servers: c.Conf.Servers,
}
// rename the current config.toml to config.toml.bak
info, err := os.Lstat(configPath)
if err != nil {
return xerrors.Errorf("Failed to lstat %s: %w", configPath, err)
}
realPath := configPath
if info.Mode()&os.ModeSymlink == os.ModeSymlink {
if realPath, err = os.Readlink(configPath); err != nil {
return xerrors.Errorf("Failed to Read link %s: %w", configPath, err)
}
}
if err := os.Rename(realPath, realPath+".bak"); err != nil {
return xerrors.Errorf("Failed to rename %s: %w", configPath, err)
}
var buf bytes.Buffer
if err := toml.NewEncoder(&buf).Encode(c); err != nil {
return xerrors.Errorf("Failed to encode to toml: %w", err)
}
str := strings.Replace(buf.String(), "\n [", "\n\n [", -1)
str = fmt.Sprintf("%s\n\n%s",
"# See README for details: https://vuls.io/docs/en/usage-settings.html",
str)
return ioutil.WriteFile(realPath, []byte(str), 0600)
}
func cleanForTOMLEncoding(server c.ServerInfo, def c.ServerInfo) c.ServerInfo {
if reflect.DeepEqual(server.Optional, def.Optional) {
server.Optional = nil
}
if def.User == server.User {
server.User = ""
}
if def.Host == server.Host {
server.Host = ""
}
if def.Port == server.Port {
server.Port = ""
}
if def.KeyPath == server.KeyPath {
server.KeyPath = ""
}
if reflect.DeepEqual(server.ScanMode, def.ScanMode) {
server.ScanMode = nil
}
if def.Type == server.Type {
server.Type = ""
}
if reflect.DeepEqual(server.CpeNames, def.CpeNames) {
server.CpeNames = nil
}
if def.OwaspDCXMLPath == server.OwaspDCXMLPath {
server.OwaspDCXMLPath = ""
}
if reflect.DeepEqual(server.IgnoreCves, def.IgnoreCves) {
server.IgnoreCves = nil
}
if reflect.DeepEqual(server.Enablerepo, def.Enablerepo) {
server.Enablerepo = nil
}
for k, v := range def.Optional {
if vv, ok := server.Optional[k]; ok && v == vv {
delete(server.Optional, k)
}
}
return server
}

View File

@@ -1,10 +1,9 @@
package report
package saas
import (
"testing"
"github.com/future-architect/vuls/config"
"github.com/future-architect/vuls/models"
)

View File

@@ -598,14 +598,26 @@ func (l *base) scanLibraries() (err error) {
return nil
}
// DummyFileInfo is a dummy struct for libscan
type DummyFileInfo struct{}
func (d *DummyFileInfo) Name() string { return "dummy" }
func (d *DummyFileInfo) Size() int64 { return 0 }
func (d *DummyFileInfo) Mode() os.FileMode { return 0 }
// Name is
func (d *DummyFileInfo) Name() string { return "dummy" }
// Size is
func (d *DummyFileInfo) Size() int64 { return 0 }
// Mode is
func (d *DummyFileInfo) Mode() os.FileMode { return 0 }
//ModTime is
func (d *DummyFileInfo) ModTime() time.Time { return time.Now() }
func (d *DummyFileInfo) IsDir() bool { return false }
func (d *DummyFileInfo) Sys() interface{} { return nil }
// IsDir is
func (d *DummyFileInfo) IsDir() bool { return false }
//Sys is
func (d *DummyFileInfo) Sys() interface{} { return nil }
func (l *base) scanWordPress() (err error) {
wpOpts := []string{l.ServerInfo.WordPress.OSUser,

View File

@@ -569,6 +569,10 @@ func ViaHTTP(header http.Header, body string) (models.ScanResult, error) {
osType = &centos{
redhatBase: redhatBase{base: base},
}
case config.Oracle:
osType = &oracle{
redhatBase: redhatBase{base: base},
}
case config.Amazon:
osType = &amazon{
redhatBase: redhatBase{base: base},

View File

@@ -63,7 +63,6 @@ func (*ReportCmd) Usage() string {
[-to-localfile]
[-to-s3]
[-to-azure-blob]
[-to-saas]
[-format-json]
[-format-xml]
[-format-one-email]
@@ -167,12 +166,8 @@ func (p *ReportCmd) SetFlags(f *flag.FlagSet) {
f.BoolVar(&c.Conf.ToHTTP, "to-http", false, "Send report via HTTP POST")
f.BoolVar(&c.Conf.ToAzureBlob, "to-azure-blob", false,
"Write report to Azure Storage blob (container/yyyyMMdd_HHmm/servername.json/xml/txt)")
f.BoolVar(&c.Conf.ToSaas, "to-saas", false,
"Upload report to Future Vuls(https://vuls.biz/) before report")
f.BoolVar(&c.Conf.GZIP, "gzip", false, "gzip compression")
f.BoolVar(&c.Conf.UUID, "uuid", false,
"Auto generate of scan target servers and then write to config.toml and scan result")
f.BoolVar(&c.Conf.Pipe, "pipe", false, "Use args passed via PIPE")
f.StringVar(&p.cveDict.Type, "cvedb-type", "",
@@ -277,88 +272,78 @@ func (p *ReportCmd) Execute(_ context.Context, f *flag.FlagSet, _ ...interface{}
pp.Sprintf("%s", c.Conf.Servers[r.ServerName]))
}
if c.Conf.UUID {
// Ensure UUIDs of scan target servers in config.toml
if err := report.EnsureUUIDs(p.configPath, res); err != nil {
util.Log.Errorf("Failed to ensure UUIDs. err: %+v", err)
util.Log.Info("Validating db config...")
if !c.Conf.ValidateOnReportDB() {
return subcommands.ExitUsageError
}
if c.Conf.CveDict.URL != "" {
if err := report.CveClient.CheckHealth(); err != nil {
util.Log.Errorf("CVE HTTP server is not running. err: %+v", err)
util.Log.Errorf("Run go-cve-dictionary as server mode before reporting or run with `-cvedb-type=sqlite3 -cvedb-sqlite3-path` option instead of -cvedb-url")
return subcommands.ExitFailure
}
}
if !c.Conf.ToSaas {
util.Log.Info("Validating db config...")
if !c.Conf.ValidateOnReportDB() {
return subcommands.ExitUsageError
}
if c.Conf.CveDict.URL != "" {
if err := report.CveClient.CheckHealth(); err != nil {
util.Log.Errorf("CVE HTTP server is not running. err: %+v", err)
util.Log.Errorf("Run go-cve-dictionary as server mode before reporting or run with `-cvedb-type=sqlite3 -cvedb-sqlite3-path` option instead of -cvedb-url")
return subcommands.ExitFailure
}
}
if c.Conf.OvalDict.URL != "" {
err := oval.Base{}.CheckHTTPHealth()
if err != nil {
util.Log.Errorf("OVAL HTTP server is not running. err: %+v", err)
util.Log.Errorf("Run goval-dictionary as server mode before reporting or run with `-ovaldb-type=sqlite3 -ovaldb-sqlite3-path` option instead of -ovaldb-url")
return subcommands.ExitFailure
}
}
if c.Conf.Gost.URL != "" {
util.Log.Infof("gost: %s", c.Conf.Gost.URL)
err := gost.Base{}.CheckHTTPHealth()
if err != nil {
util.Log.Errorf("gost HTTP server is not running. err: %+v", err)
util.Log.Errorf("Run gost as server mode before reporting or run with `-gostdb-type=sqlite3 -gostdb-sqlite3-path` option instead of -gostdb-url")
return subcommands.ExitFailure
}
}
if c.Conf.Exploit.URL != "" {
err := exploit.CheckHTTPHealth()
if err != nil {
util.Log.Errorf("exploit HTTP server is not running. err: %+v", err)
util.Log.Errorf("Run go-exploitdb as server mode before reporting")
return subcommands.ExitFailure
}
}
if c.Conf.Metasploit.URL != "" {
err := msf.CheckHTTPHealth()
if err != nil {
util.Log.Errorf("metasploit HTTP server is not running. err: %+v", err)
util.Log.Errorf("Run go-msfdb as server mode before reporting")
return subcommands.ExitFailure
}
}
dbclient, locked, err := report.NewDBClient(report.DBClientConf{
CveDictCnf: c.Conf.CveDict,
OvalDictCnf: c.Conf.OvalDict,
GostCnf: c.Conf.Gost,
ExploitCnf: c.Conf.Exploit,
MetasploitCnf: c.Conf.Metasploit,
DebugSQL: c.Conf.DebugSQL,
})
if locked {
util.Log.Errorf("SQLite3 is locked. Close other DB connections and try again. err: %+v", err)
return subcommands.ExitFailure
}
if c.Conf.OvalDict.URL != "" {
err := oval.Base{}.CheckHTTPHealth()
if err != nil {
util.Log.Errorf("Failed to init DB Clients. err: %+v", err)
util.Log.Errorf("OVAL HTTP server is not running. err: %+v", err)
util.Log.Errorf("Run goval-dictionary as server mode before reporting or run with `-ovaldb-type=sqlite3 -ovaldb-sqlite3-path` option instead of -ovaldb-url")
return subcommands.ExitFailure
}
defer dbclient.CloseDB()
}
if res, err = report.FillCveInfos(*dbclient, res, dir); err != nil {
util.Log.Errorf("%+v", err)
if c.Conf.Gost.URL != "" {
util.Log.Infof("gost: %s", c.Conf.Gost.URL)
err := gost.Base{}.CheckHTTPHealth()
if err != nil {
util.Log.Errorf("gost HTTP server is not running. err: %+v", err)
util.Log.Errorf("Run gost as server mode before reporting or run with `-gostdb-type=sqlite3 -gostdb-sqlite3-path` option instead of -gostdb-url")
return subcommands.ExitFailure
}
}
if c.Conf.Exploit.URL != "" {
err := exploit.CheckHTTPHealth()
if err != nil {
util.Log.Errorf("exploit HTTP server is not running. err: %+v", err)
util.Log.Errorf("Run go-exploitdb as server mode before reporting")
return subcommands.ExitFailure
}
}
if c.Conf.Metasploit.URL != "" {
err := msf.CheckHTTPHealth()
if err != nil {
util.Log.Errorf("metasploit HTTP server is not running. err: %+v", err)
util.Log.Errorf("Run go-msfdb as server mode before reporting")
return subcommands.ExitFailure
}
}
dbclient, locked, err := report.NewDBClient(report.DBClientConf{
CveDictCnf: c.Conf.CveDict,
OvalDictCnf: c.Conf.OvalDict,
GostCnf: c.Conf.Gost,
ExploitCnf: c.Conf.Exploit,
MetasploitCnf: c.Conf.Metasploit,
DebugSQL: c.Conf.DebugSQL,
})
if locked {
util.Log.Errorf("SQLite3 is locked. Close other DB connections and try again. err: %+v", err)
return subcommands.ExitFailure
}
if err != nil {
util.Log.Errorf("Failed to init DB Clients. err: %+v", err)
return subcommands.ExitFailure
}
defer dbclient.CloseDB()
if res, err = report.FillCveInfos(*dbclient, res, dir); err != nil {
util.Log.Errorf("%+v", err)
return subcommands.ExitFailure
}
// report
reports := []report.ResultWriter{
report.StdoutWriter{},
@@ -432,14 +417,6 @@ func (p *ReportCmd) Execute(_ context.Context, f *flag.FlagSet, _ ...interface{}
reports = append(reports, report.AzureBlobWriter{})
}
if c.Conf.ToSaas {
if !c.Conf.UUID {
util.Log.Errorf("If you use the -to-saas option, you need to enable the uuid option")
return subcommands.ExitUsageError
}
reports = append(reports, report.SaasWriter{})
}
for _, w := range reports {
if err := w.Write(res...); err != nil {
util.Log.Errorf("Failed to report. err: %+v", err)

132
subcmds/saas.go Normal file
View File

@@ -0,0 +1,132 @@
package subcmds
import (
"context"
"flag"
"os"
"path/filepath"
c "github.com/future-architect/vuls/config"
"github.com/future-architect/vuls/models"
"github.com/future-architect/vuls/report"
"github.com/future-architect/vuls/saas"
"github.com/future-architect/vuls/util"
"github.com/google/subcommands"
"github.com/k0kubun/pp"
)
// SaaSCmd is subcommand for FutureVuls
type SaaSCmd struct {
configPath string
}
// Name return subcommand name
func (*SaaSCmd) Name() string { return "saas" }
// Synopsis return synopsis
func (*SaaSCmd) Synopsis() string { return "upload to FutureVuls" }
// Usage return usage
func (*SaaSCmd) Usage() string {
return `saas:
saas
[-config=/path/to/config.toml]
[-results-dir=/path/to/results]
[-log-dir=/path/to/log]
[-http-proxy=http://192.168.0.1:8080]
[-debug]
[-debug-sql]
[-quiet]
[-no-progress]
`
}
// SetFlags set flag
func (p *SaaSCmd) SetFlags(f *flag.FlagSet) {
f.StringVar(&c.Conf.Lang, "lang", "en", "[en|ja]")
f.BoolVar(&c.Conf.Debug, "debug", false, "debug mode")
f.BoolVar(&c.Conf.DebugSQL, "debug-sql", false, "SQL debug mode")
f.BoolVar(&c.Conf.Quiet, "quiet", false, "Quiet mode. No output on stdout")
f.BoolVar(&c.Conf.NoProgress, "no-progress", false, "Suppress progress bar")
wd, _ := os.Getwd()
defaultConfPath := filepath.Join(wd, "config.toml")
f.StringVar(&p.configPath, "config", defaultConfPath, "/path/to/toml")
defaultResultsDir := filepath.Join(wd, "results")
f.StringVar(&c.Conf.ResultsDir, "results-dir", defaultResultsDir, "/path/to/results")
defaultLogDir := util.GetDefaultLogDir()
f.StringVar(&c.Conf.LogDir, "log-dir", defaultLogDir, "/path/to/log")
f.StringVar(
&c.Conf.HTTPProxy, "http-proxy", "",
"http://proxy-url:port (default: empty)")
}
// Execute execute
func (p *SaaSCmd) Execute(_ context.Context, f *flag.FlagSet, _ ...interface{}) subcommands.ExitStatus {
util.Log = util.NewCustomLogger(c.ServerInfo{})
if err := c.Load(p.configPath, ""); err != nil {
util.Log.Errorf("Error loading %s, %+v", p.configPath, err)
return subcommands.ExitUsageError
}
dir, err := report.JSONDir(f.Args())
if err != nil {
util.Log.Errorf("Failed to read from JSON: %+v", err)
return subcommands.ExitFailure
}
util.Log.Info("Validating config...")
if !c.Conf.ValidateOnReport() {
return subcommands.ExitUsageError
}
var loaded models.ScanResults
if loaded, err = report.LoadScanResults(dir); err != nil {
util.Log.Error(err)
return subcommands.ExitFailure
}
util.Log.Infof("Loaded: %s", dir)
var res models.ScanResults
hasError := false
for _, r := range loaded {
if len(r.Errors) == 0 {
res = append(res, r)
} else {
util.Log.Errorf("Ignored since errors occurred during scanning: %s, err: %v",
r.ServerName, r.Errors)
hasError = true
}
}
if len(res) == 0 {
return subcommands.ExitFailure
}
for _, r := range res {
util.Log.Debugf("%s: %s",
r.ServerInfo(),
pp.Sprintf("%s", c.Conf.Servers[r.ServerName]))
}
// Ensure UUIDs of scan target servers in config.toml
if err := saas.EnsureUUIDs(p.configPath, res); err != nil {
util.Log.Errorf("Failed to ensure UUIDs. err: %+v", err)
return subcommands.ExitFailure
}
var w report.ResultWriter = saas.Writer{}
if err := w.Write(res...); err != nil {
util.Log.Errorf("Failed to upload. err: %+v", err)
return subcommands.ExitFailure
}
if hasError {
return subcommands.ExitFailure
}
return subcommands.ExitSuccess
}

View File

@@ -47,7 +47,7 @@ type References struct {
}
// FillWordPress access to wpvulndb and fetch scurity alerts and then set to the given ScanResult.
// https://wpvulndb.com/
// https://wpscan.com/
func FillWordPress(r *models.ScanResult, token string, wpVulnCaches *map[string]string) (int, error) {
// Core
ver := strings.Replace(r.WordPressPackages.CoreVersion(), ".", "", -1)
@@ -57,7 +57,7 @@ func FillWordPress(r *models.ScanResult, token string, wpVulnCaches *map[string]
body, ok := searchCache(ver, wpVulnCaches)
if !ok {
url := fmt.Sprintf("https://wpvulndb.com/api/v3/wordpresses/%s", ver)
url := fmt.Sprintf("https://wpscan.com/api/v3/wordpresses/%s", ver)
var err error
body, err = httpRequest(url, token)
if err != nil {
@@ -87,7 +87,7 @@ func FillWordPress(r *models.ScanResult, token string, wpVulnCaches *map[string]
for _, p := range themes {
body, ok := searchCache(p.Name, wpVulnCaches)
if !ok {
url := fmt.Sprintf("https://wpvulndb.com/api/v3/themes/%s", p.Name)
url := fmt.Sprintf("https://wpscan.com/api/v3/themes/%s", p.Name)
var err error
body, err = httpRequest(url, token)
if err != nil {
@@ -113,7 +113,8 @@ func FillWordPress(r *models.ScanResult, token string, wpVulnCaches *map[string]
}
ok, err := match(pkg.Version, fixstat.FixedIn)
if err != nil {
return 0, xerrors.Errorf("Not a semantic versioning: %w", err)
util.Log.Infof("[poor] %s installed: %s, fixedIn: %s", pkg.Name, pkg.Version, fixstat.FixedIn)
continue
}
if ok {
wpVinfos = append(wpVinfos, v)
@@ -129,7 +130,7 @@ func FillWordPress(r *models.ScanResult, token string, wpVulnCaches *map[string]
for _, p := range plugins {
body, ok := searchCache(p.Name, wpVulnCaches)
if !ok {
url := fmt.Sprintf("https://wpvulndb.com/api/v3/plugins/%s", p.Name)
url := fmt.Sprintf("https://wpscan.com/api/v3/plugins/%s", p.Name)
var err error
body, err = httpRequest(url, token)
if err != nil {
@@ -155,7 +156,8 @@ func FillWordPress(r *models.ScanResult, token string, wpVulnCaches *map[string]
}
ok, err := match(pkg.Version, fixstat.FixedIn)
if err != nil {
return 0, xerrors.Errorf("Not a semantic versioning: %w", err)
util.Log.Infof("[poor] %s installed: %s, fixedIn: %s", pkg.Name, pkg.Version, fixstat.FixedIn)
continue
}
if ok {
wpVinfos = append(wpVinfos, v)