* feat: Support Ubuntu21 * feat(report): Send report via Google Chat * feat(report): Send report via Google Chat * Snip too long message as (The rest is omitted). * sorry for mixed feat-ubuntu21 branch. exlucded it * append diff, attack vector and exploits info * add ServerName filter by regexp * rename variables and rewrite validators * fix renaming miss * fix renaming miss, again
This commit is contained in:
committed by
GitHub
parent
0e0e5ce4be
commit
1c8e074c9d
@@ -42,16 +42,17 @@ type Config struct {
|
||||
Exploit ExploitConf `json:"exploit,omitempty"`
|
||||
Metasploit MetasploitConf `json:"metasploit,omitempty"`
|
||||
|
||||
Slack SlackConf `json:"-"`
|
||||
EMail SMTPConf `json:"-"`
|
||||
HTTP HTTPConf `json:"-"`
|
||||
Syslog SyslogConf `json:"-"`
|
||||
AWS AWSConf `json:"-"`
|
||||
Azure AzureConf `json:"-"`
|
||||
ChatWork ChatWorkConf `json:"-"`
|
||||
Telegram TelegramConf `json:"-"`
|
||||
WpScan WpScanConf `json:"-"`
|
||||
Saas SaasConf `json:"-"`
|
||||
Slack SlackConf `json:"-"`
|
||||
EMail SMTPConf `json:"-"`
|
||||
HTTP HTTPConf `json:"-"`
|
||||
Syslog SyslogConf `json:"-"`
|
||||
AWS AWSConf `json:"-"`
|
||||
Azure AzureConf `json:"-"`
|
||||
ChatWork ChatWorkConf `json:"-"`
|
||||
GoogleChat GoogleChatConf `json:"-"`
|
||||
Telegram TelegramConf `json:"-"`
|
||||
WpScan WpScanConf `json:"-"`
|
||||
Saas SaasConf `json:"-"`
|
||||
|
||||
ReportOpts
|
||||
}
|
||||
@@ -157,6 +158,7 @@ func (c *Config) ValidateOnReport() bool {
|
||||
&c.EMail,
|
||||
&c.Slack,
|
||||
&c.ChatWork,
|
||||
&c.GoogleChat,
|
||||
&c.Telegram,
|
||||
&c.Syslog,
|
||||
&c.HTTP,
|
||||
|
||||
32
config/googlechatconf.go
Normal file
32
config/googlechatconf.go
Normal file
@@ -0,0 +1,32 @@
|
||||
package config
|
||||
|
||||
import (
|
||||
"github.com/asaskevich/govalidator"
|
||||
"golang.org/x/xerrors"
|
||||
)
|
||||
|
||||
// GoogleChatConf is GoogleChat config
|
||||
type GoogleChatConf struct {
|
||||
WebHookURL string `valid:"url" json:"-" toml:"webHookURL,omitempty"`
|
||||
SkipIfNoCve bool `valid:"type(bool)" json:"-" toml:"skipIfNoCve"`
|
||||
ServerNameRegexp string `valid:"type(string)" json:"-" toml:"serverNameRegexp,omitempty"`
|
||||
Enabled bool `valid:"type(bool)" json:"-" toml:"-"`
|
||||
}
|
||||
|
||||
// Validate validates configuration
|
||||
func (c *GoogleChatConf) Validate() (errs []error) {
|
||||
if !c.Enabled {
|
||||
return
|
||||
}
|
||||
if len(c.WebHookURL) == 0 {
|
||||
errs = append(errs, xerrors.New("googleChatConf.webHookURL must not be empty"))
|
||||
}
|
||||
if !govalidator.IsRegex(c.ServerNameRegexp) {
|
||||
errs = append(errs, xerrors.New("googleChatConf.serverNameRegexp must be regex"))
|
||||
}
|
||||
_, err := govalidator.ValidateStruct(c)
|
||||
if err != nil {
|
||||
errs = append(errs, err)
|
||||
}
|
||||
return
|
||||
}
|
||||
102
reporter/googlechat.go
Normal file
102
reporter/googlechat.go
Normal file
@@ -0,0 +1,102 @@
|
||||
package reporter
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"context"
|
||||
"fmt"
|
||||
"net/http"
|
||||
"regexp"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"github.com/future-architect/vuls/config"
|
||||
"github.com/future-architect/vuls/models"
|
||||
"github.com/future-architect/vuls/util"
|
||||
"golang.org/x/xerrors"
|
||||
)
|
||||
|
||||
// GoogleChatWriter send report to GoogleChat
|
||||
type GoogleChatWriter struct {
|
||||
Cnf config.GoogleChatConf
|
||||
Proxy string
|
||||
}
|
||||
|
||||
func (w GoogleChatWriter) Write(rs ...models.ScanResult) (err error) {
|
||||
re := regexp.MustCompile(w.Cnf.ServerNameRegexp)
|
||||
|
||||
for _, r := range rs {
|
||||
if re.Match([]byte(r.FormatServerName())) {
|
||||
continue
|
||||
}
|
||||
msgs := []string{fmt.Sprintf("*%s*\n%s\t%s\t%s",
|
||||
r.ServerInfo(),
|
||||
r.ScannedCves.FormatCveSummary(),
|
||||
r.ScannedCves.FormatFixedStatus(r.Packages),
|
||||
r.FormatUpdatablePkgsSummary())}
|
||||
for _, vinfo := range r.ScannedCves.ToSortedSlice() {
|
||||
max := vinfo.MaxCvssScore().Value.Score
|
||||
|
||||
exploits := ""
|
||||
if 0 < len(vinfo.Exploits) || 0 < len(vinfo.Metasploits) {
|
||||
exploits = "*PoC*"
|
||||
}
|
||||
|
||||
link := ""
|
||||
if strings.HasPrefix(vinfo.CveID, "CVE-") {
|
||||
link = fmt.Sprintf("https://nvd.nist.gov/vuln/detail/%s", vinfo.CveID)
|
||||
} else if strings.HasPrefix(vinfo.CveID, "WPVDBID-") {
|
||||
link = fmt.Sprintf("https://wpscan.com/vulnerabilities/%s", strings.TrimPrefix(vinfo.CveID, "WPVDBID-"))
|
||||
}
|
||||
|
||||
msgs = append(msgs, fmt.Sprintf(`%s %s %4.1f %5s %s`,
|
||||
vinfo.CveIDDiffFormat(),
|
||||
link,
|
||||
max,
|
||||
vinfo.AttackVector(),
|
||||
exploits))
|
||||
if len(msgs) == 50 {
|
||||
msgs = append(msgs, "(The rest is omitted.)")
|
||||
break
|
||||
}
|
||||
}
|
||||
if len(msgs) == 1 && w.Cnf.SkipIfNoCve {
|
||||
msgs = []string{}
|
||||
}
|
||||
if len(msgs) != 0 {
|
||||
if err = w.postMessage(strings.Join(msgs, "\n")); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (w GoogleChatWriter) postMessage(message string) error {
|
||||
uri := fmt.Sprintf("%s", w.Cnf.WebHookURL)
|
||||
payload := `{"text": "` + message + `" }`
|
||||
|
||||
ctx, cancel := context.WithTimeout(context.Background(), 10*time.Second)
|
||||
req, err := http.NewRequestWithContext(ctx, http.MethodPost, uri, bytes.NewBuffer([]byte(payload)))
|
||||
defer cancel()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
req.Header.Add("Content-Type", "application/json; charset=utf-8")
|
||||
client, err := util.GetHTTPClient(w.Proxy)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
resp, err := client.Do(req)
|
||||
if checkResponse(resp) != nil && err != nil {
|
||||
return err
|
||||
}
|
||||
defer resp.Body.Close()
|
||||
return nil
|
||||
}
|
||||
|
||||
func (w GoogleChatWriter) checkResponse(r *http.Response) error {
|
||||
if c := r.StatusCode; 200 <= c && c <= 299 {
|
||||
return nil
|
||||
}
|
||||
return xerrors.Errorf("API call to %s failed: %s", r.Request.URL.String(), r.Status)
|
||||
}
|
||||
@@ -157,6 +157,13 @@ func printConfigToml(ips []string) (err error) {
|
||||
#room = "xxxxxxxxxxx"
|
||||
#apiToken = "xxxxxxxxxxxxxxxxxx"
|
||||
|
||||
# https://vuls.io/docs/en/config.toml.html#googlechat-section
|
||||
#[googlechat]
|
||||
#webHookURL = "https://chat.googleapis.com/v1/spaces/xxxxxxxxxx/messages?key=yyyyyyyyyy&token=zzzzzzzzzz%3D"
|
||||
#skipIfNoCve = false
|
||||
#serverNameRegexp = "^(\\[Reboot Required\\] )?((spam|ham).*|.*(egg)$)" # include spamonigiri, hamburger, boiledegg
|
||||
#serverNameRegexp = "^(\\[Reboot Required\\] )?(?:(spam|ham).*|.*(?:egg)$)" # exclude spamonigiri, hamburger, boiledegg
|
||||
|
||||
# https://vuls.io/docs/en/config.toml.html#telegram-section
|
||||
#[telegram]
|
||||
#chatID = "xxxxxxxxxxx"
|
||||
|
||||
@@ -30,15 +30,16 @@ type ReportCmd struct {
|
||||
formatList bool
|
||||
gzip bool
|
||||
|
||||
toSlack bool
|
||||
toChatWork bool
|
||||
toTelegram bool
|
||||
toEmail bool
|
||||
toSyslog bool
|
||||
toLocalFile bool
|
||||
toS3 bool
|
||||
toAzureBlob bool
|
||||
toHTTP bool
|
||||
toSlack bool
|
||||
toChatWork bool
|
||||
toGoogleChat bool
|
||||
toTelegram bool
|
||||
toEmail bool
|
||||
toSyslog bool
|
||||
toLocalFile bool
|
||||
toS3 bool
|
||||
toAzureBlob bool
|
||||
toHTTP bool
|
||||
}
|
||||
|
||||
// Name return subcommand name
|
||||
@@ -67,6 +68,7 @@ func (*ReportCmd) Usage() string {
|
||||
[-to-http]
|
||||
[-to-slack]
|
||||
[-to-chatwork]
|
||||
[-to-googlechat]
|
||||
[-to-telegram]
|
||||
[-to-localfile]
|
||||
[-to-s3]
|
||||
@@ -146,6 +148,7 @@ func (p *ReportCmd) SetFlags(f *flag.FlagSet) {
|
||||
|
||||
f.BoolVar(&p.toSlack, "to-slack", false, "Send report via Slack")
|
||||
f.BoolVar(&p.toChatWork, "to-chatwork", false, "Send report via chatwork")
|
||||
f.BoolVar(&p.toGoogleChat, "to-googlechat", false, "Send report via Google Chat")
|
||||
f.BoolVar(&p.toTelegram, "to-telegram", false, "Send report via Telegram")
|
||||
f.BoolVar(&p.toEmail, "to-email", false, "Send report via Email")
|
||||
f.BoolVar(&p.toSyslog, "to-syslog", false, "Send report via Syslog")
|
||||
@@ -173,6 +176,7 @@ func (p *ReportCmd) Execute(_ context.Context, f *flag.FlagSet, _ ...interface{}
|
||||
}
|
||||
config.Conf.Slack.Enabled = p.toSlack
|
||||
config.Conf.ChatWork.Enabled = p.toChatWork
|
||||
config.Conf.GoogleChat.Enabled = p.toGoogleChat
|
||||
config.Conf.Telegram.Enabled = p.toTelegram
|
||||
config.Conf.EMail.Enabled = p.toEmail
|
||||
config.Conf.Syslog.Enabled = p.toSyslog
|
||||
@@ -261,6 +265,10 @@ func (p *ReportCmd) Execute(_ context.Context, f *flag.FlagSet, _ ...interface{}
|
||||
reports = append(reports, reporter.ChatWorkWriter{Cnf: config.Conf.ChatWork, Proxy: config.Conf.HTTPProxy})
|
||||
}
|
||||
|
||||
if p.toGoogleChat {
|
||||
reports = append(reports, reporter.GoogleChatWriter{Cnf: config.Conf.GoogleChat, Proxy: config.Conf.HTTPProxy})
|
||||
}
|
||||
|
||||
if p.toTelegram {
|
||||
reports = append(reports, reporter.TelegramWriter{Cnf: config.Conf.Telegram})
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user