1 | /* Vuls - Vulnerability Scanner |
||
2 | Copyright (C) 2016 Future Corporation , Japan. |
||
3 | |||
4 | This program is free software: you can redistribute it and/or modify |
||
5 | it under the terms of the GNU General Public License as published by |
||
6 | the Free Software Foundation, either version 3 of the License, or |
||
7 | (at your option) any later version. |
||
8 | |||
9 | This program is distributed in the hope that it will be useful, |
||
10 | but WITHOUT ANY WARRANTY; without even the implied warranty of |
||
11 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the |
||
12 | GNU General Public License for more details. |
||
13 | |||
14 | You should have received a copy of the GNU General Public License |
||
15 | along with this program. If not, see <http://www.gnu.org/licenses/>. |
||
16 | */ |
||
17 | |||
18 | package report |
||
19 | |||
20 | import ( |
||
21 | "bytes" |
||
22 | "fmt" |
||
23 | "io/ioutil" |
||
24 | "os" |
||
25 | "reflect" |
||
26 | "regexp" |
||
27 | "sort" |
||
28 | "strings" |
||
29 | "time" |
||
30 | |||
31 | "github.com/BurntSushi/toml" |
||
32 | "github.com/future-architect/vuls/config" |
||
33 | c "github.com/future-architect/vuls/config" |
||
34 | "github.com/future-architect/vuls/contrib/owasp-dependency-check/parser" |
||
35 | "github.com/future-architect/vuls/cwe" |
||
36 | "github.com/future-architect/vuls/exploit" |
||
37 | "github.com/future-architect/vuls/github" |
||
38 | "github.com/future-architect/vuls/gost" |
||
39 | "github.com/future-architect/vuls/models" |
||
40 | "github.com/future-architect/vuls/oval" |
||
41 | "github.com/future-architect/vuls/util" |
||
42 | "github.com/future-architect/vuls/wordpress" |
||
43 | "github.com/hashicorp/uuid" |
||
44 | gostdb "github.com/knqyf263/gost/db" |
||
45 | cvedb "github.com/kotakanbe/go-cve-dictionary/db" |
||
46 | ovaldb "github.com/kotakanbe/goval-dictionary/db" |
||
47 | exploitdb "github.com/mozqnet/go-exploitdb/db" |
||
48 | "golang.org/x/xerrors" |
||
49 | ) |
||
50 | |||
51 | const ( |
||
52 | vulsOpenTag = "<vulsreport>" |
||
53 | vulsCloseTag = "</vulsreport>" |
||
54 | ) |
||
55 | |||
56 | // FillCveInfos fills CVE Detailed Information |
||
57 | func FillCveInfos(dbclient DBClient, rs []models.ScanResult, dir string) ([]models.ScanResult, error) { |
||
58 | var filledResults []models.ScanResult |
||
59 | reportedAt := time.Now() |
||
60 | hostname, _ := os.Hostname() |
||
61 | for _, r := range rs { |
||
62 | if c.Conf.RefreshCve || needToRefreshCve(r) { |
||
63 | if ovalSupported(&r) { |
||
64 | r.ScannedCves = models.VulnInfos{} |
||
65 | } |
||
66 | cpeURIs := []string{} |
||
67 | if len(r.Container.ContainerID) == 0 { |
||
68 | cpeURIs = c.Conf.Servers[r.ServerName].CpeNames |
||
69 | owaspDCXMLPath := c.Conf.Servers[r.ServerName].OwaspDCXMLPath |
||
70 | if owaspDCXMLPath != "" { |
||
71 | cpes, err := parser.Parse(owaspDCXMLPath) |
||
72 | if err != nil { |
||
73 | return nil, xerrors.Errorf("Failed to read OWASP Dependency Check XML on %s, `%s`, err: %w", |
||
0 ignored issues
–
show
introduced
by
![]() |
|||
74 | r.ServerName, owaspDCXMLPath, err) |
||
75 | } |
||
76 | cpeURIs = append(cpeURIs, cpes...) |
||
77 | } |
||
78 | } else { |
||
79 | if s, ok := c.Conf.Servers[r.ServerName]; ok { |
||
80 | if con, ok := s.Containers[r.Container.Name]; ok { |
||
81 | cpeURIs = con.Cpes |
||
82 | owaspDCXMLPath := con.OwaspDCXMLPath |
||
83 | if owaspDCXMLPath != "" { |
||
84 | cpes, err := parser.Parse(owaspDCXMLPath) |
||
85 | if err != nil { |
||
86 | return nil, xerrors.Errorf("Failed to read OWASP Dependency Check XML on %s, `%s`, err: %w", |
||
0 ignored issues
–
show
|
|||
87 | r.ServerInfo(), owaspDCXMLPath, err) |
||
88 | } |
||
89 | cpeURIs = append(cpeURIs, cpes...) |
||
90 | } |
||
91 | } |
||
92 | } |
||
93 | } |
||
94 | |||
95 | // Integrations |
||
96 | githubInts := GithubSecurityAlerts(c.Conf.Servers[r.ServerName].GitHubRepos) |
||
97 | |||
98 | wpOpt := WordPressOption{c.Conf.Servers[r.ServerName].WordPress.WPVulnDBToken} |
||
99 | |||
100 | if err := FillCveInfo(dbclient, |
||
101 | &r, |
||
102 | cpeURIs, |
||
103 | githubInts, |
||
104 | wpOpt); err != nil { |
||
105 | return nil, err |
||
106 | } |
||
107 | r.Lang = c.Conf.Lang |
||
108 | r.ReportedAt = reportedAt |
||
109 | r.ReportedVersion = c.Version |
||
110 | r.ReportedRevision = c.Revision |
||
111 | r.ReportedBy = hostname |
||
112 | r.Config.Report = c.Conf |
||
113 | r.Config.Report.Servers = map[string]c.ServerInfo{ |
||
114 | r.ServerName: c.Conf.Servers[r.ServerName], |
||
115 | } |
||
116 | if err := overwriteJSONFile(dir, r); err != nil { |
||
117 | return nil, xerrors.Errorf("Failed to write JSON: %w", err) |
||
0 ignored issues
–
show
|
|||
118 | } |
||
119 | filledResults = append(filledResults, r) |
||
120 | } else { |
||
121 | util.Log.Debugf("No need to refresh") |
||
122 | filledResults = append(filledResults, r) |
||
123 | } |
||
124 | } |
||
125 | |||
126 | if c.Conf.Diff { |
||
127 | prevs, err := loadPrevious(filledResults) |
||
128 | if err != nil { |
||
129 | return nil, err |
||
130 | } |
||
131 | |||
132 | diff, err := diff(filledResults, prevs) |
||
133 | if err != nil { |
||
134 | return nil, err |
||
135 | } |
||
136 | filledResults = []models.ScanResult{} |
||
137 | for _, r := range diff { |
||
138 | if err := fillCveDetail(dbclient.CveDB, &r); err != nil { |
||
139 | return nil, err |
||
140 | } |
||
141 | filledResults = append(filledResults, r) |
||
142 | } |
||
143 | } |
||
144 | |||
145 | filtered := []models.ScanResult{} |
||
146 | for _, r := range filledResults { |
||
147 | r = r.FilterByCvssOver(c.Conf.CvssScoreOver) |
||
148 | r = r.FilterIgnoreCves() |
||
149 | r = r.FilterUnfixed() |
||
150 | r = r.FilterIgnorePkgs() |
||
151 | r = r.FilterInactiveWordPressLibs() |
||
152 | if c.Conf.IgnoreUnscoredCves { |
||
153 | r.ScannedCves = r.ScannedCves.FindScoredVulns() |
||
154 | } |
||
155 | filtered = append(filtered, r) |
||
156 | } |
||
157 | return filtered, nil |
||
158 | } |
||
159 | |||
160 | // FillCveInfo fill scanResult with cve info. |
||
161 | func FillCveInfo(dbclient DBClient, r *models.ScanResult, cpeURIs []string, integrations ...Integration) error { |
||
162 | util.Log.Debugf("need to refresh") |
||
163 | |||
164 | nCVEs, err := FillWithOval(dbclient.OvalDB, r) |
||
165 | if err != nil { |
||
166 | return xerrors.Errorf("Failed to fill with OVAL: %w", err) |
||
0 ignored issues
–
show
|
|||
167 | } |
||
168 | util.Log.Infof("%s: %d CVEs are detected with OVAL", |
||
169 | r.FormatServerName(), nCVEs) |
||
170 | |||
171 | for i, v := range r.ScannedCves { |
||
172 | for j, p := range v.AffectedPackages { |
||
173 | if p.NotFixedYet && p.FixState == "" { |
||
174 | p.FixState = "Not fixed yet" |
||
175 | r.ScannedCves[i].AffectedPackages[j] = p |
||
176 | } |
||
177 | } |
||
178 | } |
||
179 | |||
180 | nCVEs, err = fillVulnByCpeURIs(dbclient.CveDB, r, cpeURIs) |
||
181 | if err != nil { |
||
182 | return xerrors.Errorf("Failed to detect vulns of `%s`: %w", cpeURIs, err) |
||
0 ignored issues
–
show
|
|||
183 | } |
||
184 | util.Log.Infof("%s: %d CVEs are detected with CPE", r.FormatServerName(), nCVEs) |
||
185 | |||
186 | ints := &integrationResults{} |
||
187 | for _, o := range integrations { |
||
188 | if err = o.apply(r, ints); err != nil { |
||
189 | return xerrors.Errorf("Failed to fill with integration: %w", err) |
||
0 ignored issues
–
show
|
|||
190 | } |
||
191 | } |
||
192 | util.Log.Infof("%s: %d CVEs are detected with GitHub Security Alerts", r.FormatServerName(), ints.GithubAlertsCveCounts) |
||
193 | |||
194 | nCVEs, err = FillWithGost(dbclient.GostDB, r) |
||
195 | if err != nil { |
||
196 | return xerrors.Errorf("Failed to fill with gost: %w", err) |
||
0 ignored issues
–
show
|
|||
197 | } |
||
198 | util.Log.Infof("%s: %d unfixed CVEs are detected with gost", |
||
199 | r.FormatServerName(), nCVEs) |
||
200 | |||
201 | util.Log.Infof("Fill CVE detailed information with CVE-DB") |
||
202 | if err := fillCveDetail(dbclient.CveDB, r); err != nil { |
||
203 | return xerrors.Errorf("Failed to fill with CVE: %w", err) |
||
0 ignored issues
–
show
|
|||
204 | } |
||
205 | |||
206 | util.Log.Infof("Fill exploit information with Exploit-DB") |
||
207 | nExploitCve, err := FillWithExploit(dbclient.ExploitDB, r) |
||
208 | if err != nil { |
||
209 | return xerrors.Errorf("Failed to fill with exploit: %w", err) |
||
0 ignored issues
–
show
|
|||
210 | } |
||
211 | util.Log.Infof("%s: %d exploits are detected", |
||
212 | r.FormatServerName(), nExploitCve) |
||
213 | |||
214 | enAlertCnt, jaAlertCnt := fillAlerts(r) |
||
215 | util.Log.Infof("%s: en: %d, ja: %d alerts are detected", |
||
216 | r.FormatServerName(), enAlertCnt, jaAlertCnt) |
||
217 | |||
218 | fillCweDict(r) |
||
219 | return nil |
||
220 | } |
||
221 | |||
222 | // fillCveDetail fetches NVD, JVN from CVE Database |
||
223 | func fillCveDetail(driver cvedb.DB, r *models.ScanResult) error { |
||
224 | var cveIDs []string |
||
225 | for _, v := range r.ScannedCves { |
||
226 | cveIDs = append(cveIDs, v.CveID) |
||
227 | } |
||
228 | |||
229 | ds, err := CveClient.FetchCveDetails(driver, cveIDs) |
||
230 | if err != nil { |
||
231 | return err |
||
232 | } |
||
233 | for _, d := range ds { |
||
234 | nvd := models.ConvertNvdJSONToModel(d.CveID, d.NvdJSON) |
||
235 | if nvd == nil { |
||
236 | nvd = models.ConvertNvdXMLToModel(d.CveID, d.NvdXML) |
||
237 | } |
||
238 | jvn := models.ConvertJvnToModel(d.CveID, d.Jvn) |
||
239 | |||
240 | for cveID, vinfo := range r.ScannedCves { |
||
241 | if vinfo.CveID == d.CveID { |
||
242 | if vinfo.CveContents == nil { |
||
243 | vinfo.CveContents = models.CveContents{} |
||
244 | } |
||
245 | for _, con := range []*models.CveContent{nvd, jvn} { |
||
246 | if con != nil && !con.Empty() { |
||
247 | vinfo.CveContents[con.Type] = *con |
||
248 | } |
||
249 | } |
||
250 | r.ScannedCves[cveID] = vinfo |
||
251 | break |
||
252 | } |
||
253 | } |
||
254 | } |
||
255 | return nil |
||
256 | } |
||
257 | |||
258 | // FillWithOval fetches OVAL database |
||
259 | func FillWithOval(driver ovaldb.DB, r *models.ScanResult) (nCVEs int, err error) { |
||
260 | var ovalClient oval.Client |
||
261 | var ovalFamily string |
||
262 | |||
263 | switch r.Family { |
||
264 | case c.Debian: |
||
265 | ovalClient = oval.NewDebian() |
||
266 | ovalFamily = c.Debian |
||
267 | case c.Ubuntu: |
||
268 | ovalClient = oval.NewUbuntu() |
||
269 | ovalFamily = c.Ubuntu |
||
270 | case c.RedHat: |
||
271 | ovalClient = oval.NewRedhat() |
||
272 | ovalFamily = c.RedHat |
||
273 | case c.CentOS: |
||
274 | ovalClient = oval.NewCentOS() |
||
275 | //use RedHat's OVAL |
||
276 | ovalFamily = c.RedHat |
||
277 | case c.Oracle: |
||
278 | ovalClient = oval.NewOracle() |
||
279 | ovalFamily = c.Oracle |
||
280 | case c.SUSEEnterpriseServer: |
||
281 | // TODO other suse family |
||
282 | ovalClient = oval.NewSUSE() |
||
283 | ovalFamily = c.SUSEEnterpriseServer |
||
284 | case c.Alpine: |
||
285 | ovalClient = oval.NewAlpine() |
||
286 | ovalFamily = c.Alpine |
||
287 | case c.Amazon, c.Raspbian, c.FreeBSD, c.Windows: |
||
288 | return 0, nil |
||
289 | case c.ServerTypePseudo: |
||
290 | return 0, nil |
||
291 | default: |
||
292 | if r.Family == "" { |
||
293 | return 0, xerrors.New("Probably an error occurred during scanning. Check the error message") |
||
294 | } |
||
295 | return 0, xerrors.Errorf("OVAL for %s is not implemented yet", r.Family) |
||
296 | } |
||
297 | |||
298 | if !c.Conf.OvalDict.IsFetchViaHTTP() { |
||
299 | if driver == nil { |
||
300 | return 0, nil |
||
301 | } |
||
302 | if err = driver.NewOvalDB(ovalFamily); err != nil { |
||
303 | return 0, xerrors.Errorf("Failed to New Oval DB. err: %w", err) |
||
0 ignored issues
–
show
|
|||
304 | } |
||
305 | } |
||
306 | |||
307 | util.Log.Debugf("Check whether oval fetched: %s %s", ovalFamily, r.Release) |
||
308 | ok, err := ovalClient.CheckIfOvalFetched(driver, ovalFamily, r.Release) |
||
309 | if err != nil { |
||
310 | return 0, err |
||
311 | } |
||
312 | if !ok { |
||
313 | util.Log.Warnf("OVAL entries of %s %s are not found. It's recommended to use OVAL to improve scanning accuracy. For details, see https://github.com/kotakanbe/goval-dictionary#usage , Then report with --ovaldb-path or --ovaldb-url flag", ovalFamily, r.Release) |
||
314 | return 0, nil |
||
315 | } |
||
316 | |||
317 | _, err = ovalClient.CheckIfOvalFresh(driver, ovalFamily, r.Release) |
||
318 | if err != nil { |
||
319 | return 0, err |
||
320 | } |
||
321 | |||
322 | return ovalClient.FillWithOval(driver, r) |
||
323 | } |
||
324 | |||
325 | // FillWithGost fills CVEs with gost dataabase |
||
326 | // https://github.com/knqyf263/gost |
||
327 | func FillWithGost(driver gostdb.DB, r *models.ScanResult) (nCVEs int, err error) { |
||
328 | gostClient := gost.NewClient(r.Family) |
||
329 | // TODO chekc if fetched |
||
330 | // TODO chekc if fresh enough |
||
331 | return gostClient.FillWithGost(driver, r) |
||
332 | } |
||
333 | |||
334 | // FillWithExploit fills Exploits with exploit dataabase |
||
335 | // https://github.com/mozqnet/go-exploitdb |
||
336 | func FillWithExploit(driver exploitdb.DB, r *models.ScanResult) (nExploitCve int, err error) { |
||
337 | // TODO chekc if fetched |
||
338 | // TODO chekc if fresh enough |
||
339 | return exploit.FillWithExploit(driver, r) |
||
340 | } |
||
341 | |||
342 | func fillVulnByCpeURIs(driver cvedb.DB, r *models.ScanResult, cpeURIs []string) (nCVEs int, err error) { |
||
343 | for _, name := range cpeURIs { |
||
344 | details, err := CveClient.FetchCveDetailsByCpeName(driver, name) |
||
345 | if err != nil { |
||
346 | return 0, err |
||
347 | } |
||
348 | for _, detail := range details { |
||
349 | if val, ok := r.ScannedCves[detail.CveID]; ok { |
||
350 | names := val.CpeURIs |
||
351 | names = util.AppendIfMissing(names, name) |
||
352 | val.CpeURIs = names |
||
353 | val.Confidences.AppendIfMissing(models.CpeNameMatch) |
||
354 | r.ScannedCves[detail.CveID] = val |
||
355 | } else { |
||
356 | v := models.VulnInfo{ |
||
357 | CveID: detail.CveID, |
||
358 | CpeURIs: []string{name}, |
||
359 | Confidences: models.Confidences{models.CpeNameMatch}, |
||
360 | } |
||
361 | r.ScannedCves[detail.CveID] = v |
||
362 | nCVEs++ |
||
363 | } |
||
364 | } |
||
365 | } |
||
366 | return nCVEs, nil |
||
367 | } |
||
368 | |||
369 | type integrationResults struct { |
||
370 | GithubAlertsCveCounts int |
||
371 | WordPressCveCounts int |
||
372 | } |
||
373 | |||
374 | // Integration is integration of vuls report |
||
375 | type Integration interface { |
||
376 | apply(*models.ScanResult, *integrationResults) error |
||
377 | } |
||
378 | |||
379 | // GithubSecurityAlerts : |
||
380 | func GithubSecurityAlerts(githubConfs map[string]config.GitHubConf) Integration { |
||
381 | return GithubSecurityAlertOption{ |
||
382 | GithubConfs: githubConfs, |
||
383 | } |
||
384 | } |
||
385 | |||
386 | // GithubSecurityAlertOption : |
||
387 | type GithubSecurityAlertOption struct { |
||
388 | GithubConfs map[string]config.GitHubConf |
||
389 | } |
||
390 | |||
391 | // https://help.github.com/articles/about-security-alerts-for-vulnerable-dependencies/ |
||
392 | func (g GithubSecurityAlertOption) apply(r *models.ScanResult, ints *integrationResults) (err error) { |
||
393 | var nCVEs int |
||
394 | for ownerRepo, setting := range g.GithubConfs { |
||
395 | ss := strings.Split(ownerRepo, "/") |
||
396 | owner, repo := ss[0], ss[1] |
||
397 | n, err := github.FillGitHubSecurityAlerts(r, owner, repo, setting.Token) |
||
398 | if err != nil { |
||
399 | return xerrors.Errorf("Failed to access GitHub Security Alerts: %w", err) |
||
0 ignored issues
–
show
|
|||
400 | } |
||
401 | nCVEs += n |
||
402 | } |
||
403 | ints.GithubAlertsCveCounts = nCVEs |
||
404 | return nil |
||
405 | } |
||
406 | |||
407 | // WordPressOption : |
||
408 | type WordPressOption struct { |
||
409 | token string |
||
410 | } |
||
411 | |||
412 | func (g WordPressOption) apply(r *models.ScanResult, ints *integrationResults) (err error) { |
||
413 | if g.token == "" { |
||
414 | return nil |
||
415 | } |
||
416 | n, err := wordpress.FillWordPress(r, g.token) |
||
417 | if err != nil { |
||
418 | return xerrors.Errorf("Failed to fetch from WPVulnDB. Check the WPVulnDBToken in config.toml. err: %w", err) |
||
0 ignored issues
–
show
|
|||
419 | } |
||
420 | ints.WordPressCveCounts = n |
||
421 | return nil |
||
422 | } |
||
423 | |||
424 | func fillCweDict(r *models.ScanResult) { |
||
425 | uniqCweIDMap := map[string]bool{} |
||
426 | for _, vinfo := range r.ScannedCves { |
||
427 | for _, cont := range vinfo.CveContents { |
||
428 | for _, id := range cont.CweIDs { |
||
429 | if strings.HasPrefix(id, "CWE-") { |
||
430 | id = strings.TrimPrefix(id, "CWE-") |
||
431 | uniqCweIDMap[id] = true |
||
432 | } |
||
433 | } |
||
434 | } |
||
435 | } |
||
436 | |||
437 | dict := map[string]models.CweDictEntry{} |
||
438 | for id := range uniqCweIDMap { |
||
439 | entry := models.CweDictEntry{} |
||
440 | if e, ok := cwe.CweDictEn[id]; ok { |
||
441 | if rank, ok := cwe.OwaspTopTen2017[id]; ok { |
||
442 | entry.OwaspTopTen2017 = rank |
||
443 | } |
||
444 | entry.En = &e |
||
445 | } else { |
||
446 | util.Log.Debugf("CWE-ID %s is not found in English CWE Dict", id) |
||
447 | entry.En = &cwe.Cwe{CweID: id} |
||
448 | } |
||
449 | |||
450 | if c.Conf.Lang == "ja" { |
||
451 | if e, ok := cwe.CweDictJa[id]; ok { |
||
452 | if rank, ok := cwe.OwaspTopTen2017[id]; ok { |
||
453 | entry.OwaspTopTen2017 = rank |
||
454 | } |
||
455 | entry.Ja = &e |
||
456 | } else { |
||
457 | util.Log.Debugf("CWE-ID %s is not found in Japanese CWE Dict", id) |
||
458 | entry.Ja = &cwe.Cwe{CweID: id} |
||
459 | } |
||
460 | } |
||
461 | dict[id] = entry |
||
462 | } |
||
463 | r.CweDict = dict |
||
464 | return |
||
465 | } |
||
466 | |||
467 | func fillAlerts(r *models.ScanResult) (enCnt int, jaCnt int) { |
||
468 | for cveID, vuln := range r.ScannedCves { |
||
469 | enAs, jaAs := models.GetAlertsByCveID(cveID, "en"), models.GetAlertsByCveID(cveID, "ja") |
||
470 | vuln.AlertDict = models.AlertDict{ |
||
471 | Ja: jaAs, |
||
472 | En: enAs, |
||
473 | } |
||
474 | r.ScannedCves[cveID] = vuln |
||
475 | enCnt += len(enAs) |
||
476 | jaCnt += len(jaAs) |
||
477 | } |
||
478 | return enCnt, jaCnt |
||
479 | } |
||
480 | |||
481 | const reUUID = "[\\da-f]{8}-[\\da-f]{4}-[\\da-f]{4}-[\\da-f]{4}-[\\da-f]{12}" |
||
482 | |||
483 | // EnsureUUIDs generate a new UUID of the scan target server if UUID is not assigned yet. |
||
484 | // And then set the generated UUID to config.toml and scan results. |
||
485 | func EnsureUUIDs(configPath string, results models.ScanResults) error { |
||
486 | // Sort Host->Container |
||
487 | sort.Slice(results, func(i, j int) bool { |
||
488 | if results[i].ServerName == results[j].ServerName { |
||
489 | return results[i].Container.ContainerID < results[j].Container.ContainerID |
||
490 | } |
||
491 | return results[i].ServerName < results[j].ServerName |
||
492 | }) |
||
493 | |||
494 | for i, r := range results { |
||
495 | server := c.Conf.Servers[r.ServerName] |
||
496 | if server.UUIDs == nil { |
||
497 | server.UUIDs = map[string]string{} |
||
498 | } |
||
499 | |||
500 | name := "" |
||
501 | if r.IsContainer() { |
||
502 | name = fmt.Sprintf("%s@%s", r.Container.Name, r.ServerName) |
||
503 | |||
504 | // Scanning with the -containers-only flag at scan time, the UUID of Container Host may not be generated, |
||
505 | // so check it. Otherwise create a UUID of the Container Host and set it. |
||
506 | serverUUID := "" |
||
507 | if id, ok := server.UUIDs[r.ServerName]; !ok { |
||
508 | serverUUID = uuid.GenerateUUID() |
||
509 | } else { |
||
510 | matched, err := regexp.MatchString(reUUID, id) |
||
511 | if !matched || err != nil { |
||
512 | serverUUID = uuid.GenerateUUID() |
||
513 | } |
||
514 | } |
||
515 | if serverUUID != "" { |
||
516 | server.UUIDs[r.ServerName] = serverUUID |
||
517 | } |
||
518 | } else { |
||
519 | name = r.ServerName |
||
520 | } |
||
521 | |||
522 | if id, ok := server.UUIDs[name]; ok { |
||
523 | matched, err := regexp.MatchString(reUUID, id) |
||
524 | if !matched || err != nil { |
||
525 | util.Log.Warnf("UUID is invalid. Re-generate UUID %s: %s", id, err) |
||
526 | } else { |
||
527 | if r.IsContainer() { |
||
528 | results[i].Container.UUID = id |
||
529 | results[i].ServerUUID = server.UUIDs[r.ServerName] |
||
530 | } else { |
||
531 | results[i].ServerUUID = id |
||
532 | } |
||
533 | // continue if the UUID has already assigned and valid |
||
534 | continue |
||
535 | } |
||
536 | } |
||
537 | |||
538 | // Generate a new UUID and set to config and scan result |
||
539 | id := uuid.GenerateUUID() |
||
540 | server.UUIDs[name] = id |
||
541 | server = cleanForTOMLEncoding(server, c.Conf.Default) |
||
542 | c.Conf.Servers[r.ServerName] = server |
||
543 | |||
544 | if r.IsContainer() { |
||
545 | results[i].Container.UUID = id |
||
546 | results[i].ServerUUID = server.UUIDs[r.ServerName] |
||
547 | } else { |
||
548 | results[i].ServerUUID = id |
||
549 | } |
||
550 | } |
||
551 | |||
552 | for name, server := range c.Conf.Servers { |
||
553 | server = cleanForTOMLEncoding(server, c.Conf.Default) |
||
554 | c.Conf.Servers[name] = server |
||
555 | } |
||
556 | |||
557 | email := &c.Conf.EMail |
||
558 | if email.SMTPAddr == "" { |
||
559 | email = nil |
||
560 | } |
||
561 | |||
562 | slack := &c.Conf.Slack |
||
563 | if slack.HookURL == "" { |
||
564 | slack = nil |
||
565 | } |
||
566 | |||
567 | cveDict := &c.Conf.CveDict |
||
568 | ovalDict := &c.Conf.OvalDict |
||
569 | gost := &c.Conf.Gost |
||
570 | exploit := &c.Conf.Exploit |
||
571 | http := &c.Conf.HTTP |
||
572 | if http.URL == "" { |
||
573 | http = nil |
||
574 | } |
||
575 | |||
576 | syslog := &c.Conf.Syslog |
||
577 | if syslog.Host == "" { |
||
578 | syslog = nil |
||
579 | } |
||
580 | |||
581 | aws := &c.Conf.AWS |
||
582 | if aws.S3Bucket == "" { |
||
583 | aws = nil |
||
584 | } |
||
585 | |||
586 | azure := &c.Conf.Azure |
||
587 | if azure.AccountName == "" { |
||
588 | azure = nil |
||
589 | } |
||
590 | |||
591 | stride := &c.Conf.Stride |
||
592 | if stride.HookURL == "" { |
||
593 | stride = nil |
||
594 | } |
||
595 | |||
596 | hipChat := &c.Conf.HipChat |
||
597 | if hipChat.AuthToken == "" { |
||
598 | hipChat = nil |
||
599 | } |
||
600 | |||
601 | chatWork := &c.Conf.ChatWork |
||
602 | if chatWork.APIToken == "" { |
||
603 | chatWork = nil |
||
604 | } |
||
605 | |||
606 | saas := &c.Conf.Saas |
||
607 | if saas.GroupID == 0 { |
||
608 | saas = nil |
||
609 | } |
||
610 | |||
611 | c := struct { |
||
612 | CveDict *c.GoCveDictConf `toml:"cveDict"` |
||
613 | OvalDict *c.GovalDictConf `toml:"ovalDict"` |
||
614 | Gost *c.GostConf `toml:"gost"` |
||
615 | Exploit *c.ExploitConf `toml:"exploit"` |
||
616 | Slack *c.SlackConf `toml:"slack"` |
||
617 | Email *c.SMTPConf `toml:"email"` |
||
618 | HTTP *c.HTTPConf `toml:"http"` |
||
619 | Syslog *c.SyslogConf `toml:"syslog"` |
||
620 | AWS *c.AWS `toml:"aws"` |
||
621 | Azure *c.Azure `toml:"azure"` |
||
622 | Stride *c.StrideConf `toml:"stride"` |
||
623 | HipChat *c.HipChatConf `toml:"hipChat"` |
||
624 | ChatWork *c.ChatWorkConf `toml:"chatWork"` |
||
625 | Saas *c.SaasConf `toml:"saas"` |
||
626 | |||
627 | Default c.ServerInfo `toml:"default"` |
||
628 | Servers map[string]c.ServerInfo `toml:"servers"` |
||
629 | }{ |
||
630 | CveDict: cveDict, |
||
631 | OvalDict: ovalDict, |
||
632 | Gost: gost, |
||
633 | Exploit: exploit, |
||
634 | Slack: slack, |
||
635 | Email: email, |
||
636 | HTTP: http, |
||
637 | Syslog: syslog, |
||
638 | AWS: aws, |
||
639 | Azure: azure, |
||
640 | Stride: stride, |
||
641 | HipChat: hipChat, |
||
642 | ChatWork: chatWork, |
||
643 | Saas: saas, |
||
644 | |||
645 | Default: c.Conf.Default, |
||
646 | Servers: c.Conf.Servers, |
||
647 | } |
||
648 | |||
649 | // rename the current config.toml to config.toml.bak |
||
650 | info, err := os.Lstat(configPath) |
||
651 | if err != nil { |
||
652 | return xerrors.Errorf("Failed to lstat %s: %w", configPath, err) |
||
0 ignored issues
–
show
|
|||
653 | } |
||
654 | realPath := configPath |
||
655 | if info.Mode()&os.ModeSymlink == os.ModeSymlink { |
||
656 | if realPath, err = os.Readlink(configPath); err != nil { |
||
657 | return xerrors.Errorf("Failed to Read link %s: %w", configPath, err) |
||
0 ignored issues
–
show
|
|||
658 | } |
||
659 | } |
||
660 | if err := os.Rename(realPath, realPath+".bak"); err != nil { |
||
661 | return xerrors.Errorf("Failed to rename %s: %w", configPath, err) |
||
0 ignored issues
–
show
|
|||
662 | } |
||
663 | |||
664 | var buf bytes.Buffer |
||
665 | if err := toml.NewEncoder(&buf).Encode(c); err != nil { |
||
666 | return xerrors.Errorf("Failed to encode to toml: %w", err) |
||
0 ignored issues
–
show
|
|||
667 | } |
||
668 | str := strings.Replace(buf.String(), "\n [", "\n\n [", -1) |
||
669 | str = fmt.Sprintf("%s\n\n%s", |
||
670 | "# See REAME for details: https://vuls.io/docs/en/usage-settings.html", |
||
671 | str) |
||
672 | |||
673 | return ioutil.WriteFile(realPath, []byte(str), 0600) |
||
674 | } |
||
675 | |||
676 | func cleanForTOMLEncoding(server c.ServerInfo, def c.ServerInfo) c.ServerInfo { |
||
677 | if reflect.DeepEqual(server.Optional, def.Optional) { |
||
678 | server.Optional = nil |
||
679 | } |
||
680 | |||
681 | if def.User == server.User { |
||
682 | server.User = "" |
||
683 | } |
||
684 | |||
685 | if def.Host == server.Host { |
||
686 | server.Host = "" |
||
687 | } |
||
688 | |||
689 | if def.Port == server.Port { |
||
690 | server.Port = "" |
||
691 | } |
||
692 | |||
693 | if def.KeyPath == server.KeyPath { |
||
694 | server.KeyPath = "" |
||
695 | } |
||
696 | |||
697 | if reflect.DeepEqual(server.ScanMode, def.ScanMode) { |
||
698 | server.ScanMode = nil |
||
699 | } |
||
700 | |||
701 | if def.Type == server.Type { |
||
702 | server.Type = "" |
||
703 | } |
||
704 | |||
705 | if reflect.DeepEqual(server.CpeNames, def.CpeNames) { |
||
706 | server.CpeNames = nil |
||
707 | } |
||
708 | |||
709 | if def.OwaspDCXMLPath == server.OwaspDCXMLPath { |
||
710 | server.OwaspDCXMLPath = "" |
||
711 | } |
||
712 | |||
713 | if reflect.DeepEqual(server.IgnoreCves, def.IgnoreCves) { |
||
714 | server.IgnoreCves = nil |
||
715 | } |
||
716 | |||
717 | if reflect.DeepEqual(server.Enablerepo, def.Enablerepo) { |
||
718 | server.Enablerepo = nil |
||
719 | } |
||
720 | |||
721 | for k, v := range def.Optional { |
||
722 | if vv, ok := server.Optional[k]; ok && v == vv { |
||
723 | delete(server.Optional, k) |
||
724 | } |
||
725 | } |
||
726 | |||
727 | return server |
||
728 | } |
||
729 |