Issues (121)

models/scanresults.go (1 issue)

Severity
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 models
19
20
import (
21
	"bytes"
22
	"fmt"
23
	"regexp"
24
	"strings"
25
	"time"
26
27
	"github.com/future-architect/vuls/alert"
28
29
	"github.com/future-architect/vuls/config"
30
	"github.com/future-architect/vuls/cwe"
31
	"github.com/future-architect/vuls/util"
32
)
33
34
// ScanResults is a slide of ScanResult
35
type ScanResults []ScanResult
36
37
// ScanResult has the result of scanned CVE information.
38
type ScanResult struct {
39
	JSONVersion      int       `json:"jsonVersion"`
40
	Lang             string    `json:"lang"`
41
	ServerUUID       string    `json:"serverUUID"`
42
	ServerName       string    `json:"serverName"` // TOML Section key
43
	Family           string    `json:"family"`
44
	Release          string    `json:"release"`
45
	Container        Container `json:"container"`
46
	Platform         Platform  `json:"platform"`
47
	IPv4Addrs        []string  `json:"ipv4Addrs,omitempty"` // only global unicast address (https://golang.org/pkg/net/#IP.IsGlobalUnicast)
48
	IPv6Addrs        []string  `json:"ipv6Addrs,omitempty"` // only global unicast address (https://golang.org/pkg/net/#IP.IsGlobalUnicast)
49
	ScannedAt        time.Time `json:"scannedAt"`
50
	ScanMode         string    `json:"scanMode"`
51
	ScannedVersion   string    `json:"scannedVersion"`
52
	ScannedRevision  string    `json:"scannedRevision"`
53
	ScannedBy        string    `json:"scannedBy"`
54
	ScannedVia       string    `json:"scannedVia"`
55
	ScannedIPv4Addrs []string  `json:"scannedIpv4Addrs,omitempty"`
56
	ScannedIPv6Addrs []string  `json:"scannedIpv6Addrs,omitempty"`
57
	ReportedAt       time.Time `json:"reportedAt"`
58
	ReportedVersion  string    `json:"reportedVersion"`
59
	ReportedRevision string    `json:"reportedRevision"`
60
	ReportedBy       string    `json:"reportedBy"`
61
	Errors           []string  `json:"errors"`
62
63
	ScannedCves       VulnInfos              `json:"scannedCves"`
64
	RunningKernel     Kernel                 `json:"runningKernel"`
65
	Packages          Packages               `json:"packages"`
66
	SrcPackages       SrcPackages            `json:",omitempty"`
67
	WordPressPackages *WordPressPackages     `json:",omitempty"`
68
	CweDict           CweDict                `json:"cweDict,omitempty"`
69
	Optional          map[string]interface{} `json:",omitempty"`
70
	Config            struct {
71
		Scan   config.Config `json:"scan"`
72
		Report config.Config `json:"report"`
73
	} `json:"config"`
74
}
75
76
// CweDict is a dictionary for CWE
77
type CweDict map[string]CweDictEntry
78
79
// Get the name, url, top10URL for the specified cweID, lang
80
func (c CweDict) Get(cweID, lang string) (name, url, top10Rank, top10URL string) {
81
	cweNum := strings.TrimPrefix(cweID, "CWE-")
82
	switch config.Conf.Lang {
83
	case "ja":
84
		if dict, ok := c[cweNum]; ok && dict.OwaspTopTen2017 != "" {
85
			top10Rank = dict.OwaspTopTen2017
86
			top10URL = cwe.OwaspTopTen2017GitHubURLJa[dict.OwaspTopTen2017]
87
		}
88
		if dict, ok := cwe.CweDictJa[cweNum]; ok {
89
			name = dict.Name
90
			url = fmt.Sprintf("http://jvndb.jvn.jp/ja/cwe/%s.html", cweID)
91
		} else {
92
			if dict, ok := cwe.CweDictEn[cweNum]; ok {
93
				name = dict.Name
94
			}
95
			url = fmt.Sprintf("https://cwe.mitre.org/data/definitions/%s.html", cweID)
96
		}
97
	default:
98
		if dict, ok := c[cweNum]; ok && dict.OwaspTopTen2017 != "" {
99
			top10Rank = dict.OwaspTopTen2017
100
			top10URL = cwe.OwaspTopTen2017GitHubURLEn[dict.OwaspTopTen2017]
101
		}
102
		url = fmt.Sprintf("https://cwe.mitre.org/data/definitions/%s.html", cweID)
103
		if dict, ok := cwe.CweDictEn[cweNum]; ok {
104
			name = dict.Name
105
		}
106
	}
107
	return
108
}
109
110
// CweDictEntry is a entry of CWE
111
type CweDictEntry struct {
112
	En              *cwe.Cwe `json:"en,omitempty"`
113
	Ja              *cwe.Cwe `json:"ja,omitempty"`
114
	OwaspTopTen2017 string   `json:"owaspTopTen2017"`
115
}
116
117
// GetAlertsByCveID return alerts fetched by cveID
118
func GetAlertsByCveID(cveID string, lang string) (alerts []alert.Alert) {
119
	alerts = alert.GenerateAlertDict(cveID, lang)
120
	return alerts
121
}
122
123
// Kernel has the Release, version and whether need restart
124
type Kernel struct {
125
	Release        string `json:"release"`
126
	Version        string `json:"version"`
127
	RebootRequired bool   `json:"rebootRequired"`
128
}
129
130
// FilterByCvssOver is filter function.
131
func (r ScanResult) FilterByCvssOver(over float64) ScanResult {
132
	filtered := r.ScannedCves.Find(func(v VulnInfo) bool {
133
		v2Max := v.MaxCvss2Score()
134
		v3Max := v.MaxCvss3Score()
135
		max := v2Max.Value.Score
136
		if max < v3Max.Value.Score {
137
			max = v3Max.Value.Score
138
		}
139
		if over <= max {
140
			return true
141
		}
142
		return false
143
	})
144
	r.ScannedCves = filtered
145
	return r
146
}
147
148
// FilterIgnoreCves is filter function.
149
func (r ScanResult) FilterIgnoreCves() ScanResult {
150
151
	ignoreCves := []string{}
152
	if len(r.Container.Name) == 0 {
153
		ignoreCves = config.Conf.Servers[r.ServerName].IgnoreCves
154
	} else {
155
		if s, ok := config.Conf.Servers[r.ServerName]; ok {
156
			if con, ok := s.Containers[r.Container.Name]; ok {
157
				ignoreCves = con.IgnoreCves
158
			} else {
159
				util.Log.Errorf("%s is not found in config.toml",
160
					r.Container.Name)
161
				return r
162
			}
163
		} else {
164
			util.Log.Errorf("%s is not found in config.toml",
165
				r.ServerName)
166
			return r
167
		}
168
	}
169
170
	filtered := r.ScannedCves.Find(func(v VulnInfo) bool {
171
		for _, c := range ignoreCves {
172
			if v.CveID == c {
173
				return false
174
			}
175
		}
176
		return true
177
	})
178
	r.ScannedCves = filtered
179
	return r
180
}
181
182
// FilterUnfixed is filter function.
183
func (r ScanResult) FilterUnfixed() ScanResult {
184
	if !config.Conf.IgnoreUnfixed {
185
		return r
186
	}
187
	filtered := r.ScannedCves.Find(func(v VulnInfo) bool {
188
		// Report cves detected by CPE because Vuls can't know 'fixed' or 'unfixed'
189
		if len(v.CpeURIs) != 0 {
190
			return true
191
		}
192
		NotFixedAll := true
193
		for _, p := range v.AffectedPackages {
194
			NotFixedAll = NotFixedAll && p.NotFixedYet
195
		}
196
		return !NotFixedAll
197
	})
198
	r.ScannedCves = filtered
199
	return r
200
}
201
202
// FilterIgnorePkgs is filter function.
203
func (r ScanResult) FilterIgnorePkgs() ScanResult {
204
	ignorePkgsRegexps := []string{}
205
	if len(r.Container.Name) == 0 {
206
		ignorePkgsRegexps = config.Conf.Servers[r.ServerName].IgnorePkgsRegexp
207
	} else {
208
		if s, ok := config.Conf.Servers[r.ServerName]; ok {
209
			if con, ok := s.Containers[r.Container.Name]; ok {
210
				ignorePkgsRegexps = con.IgnorePkgsRegexp
211
			} else {
212
				util.Log.Errorf("%s is not found in config.toml",
213
					r.Container.Name)
214
				return r
215
			}
216
		} else {
217
			util.Log.Errorf("%s is not found in config.toml",
218
				r.ServerName)
219
			return r
220
		}
221
	}
222
223
	regexps := []*regexp.Regexp{}
224
	for _, pkgRegexp := range ignorePkgsRegexps {
225
		re, err := regexp.Compile(pkgRegexp)
226
		if err != nil {
227
			util.Log.Errorf("Faild to parse %s. err: %+v", pkgRegexp, err)
228
			continue
229
		} else {
230
			regexps = append(regexps, re)
231
		}
232
	}
233
	if len(regexps) == 0 {
234
		return r
235
	}
236
237
	filtered := r.ScannedCves.Find(func(v VulnInfo) bool {
238
		if len(v.AffectedPackages) == 0 {
239
			return true
240
		}
241
		for _, p := range v.AffectedPackages {
242
			match := false
243
			for _, re := range regexps {
244
				if re.MatchString(p.Name) {
245
					match = true
246
				}
247
			}
248
			if !match {
249
				return true
250
			}
251
		}
252
		return false
253
	})
254
255
	r.ScannedCves = filtered
256
	return r
257
}
258
259
// FilterInactiveWordPressLibs is filter function.
260
func (r ScanResult) FilterInactiveWordPressLibs() ScanResult {
261
	if !config.Conf.Servers[r.ServerName].WordPress.IgnoreInactive {
262
		return r
263
	}
264
265
	filtered := r.ScannedCves.Find(func(v VulnInfo) bool {
266
		if len(v.WpPackageFixStats) == 0 {
267
			return true
268
		}
269
		// Ignore if all libs in this vulnInfo inactive
270
		for _, wp := range v.WpPackageFixStats {
271
			if p, ok := r.WordPressPackages.Find(wp.Name); ok {
272
				if p.Status != Inactive {
273
					return true
274
				}
275
			}
276
		}
277
		return false
278
	})
279
	r.ScannedCves = filtered
280
	return r
281
}
282
283
// ReportFileName returns the filename on localhost without extention
284
func (r ScanResult) ReportFileName() (name string) {
285
	if len(r.Container.ContainerID) == 0 {
286
		return fmt.Sprintf("%s", r.ServerName)
287
	}
288
	return fmt.Sprintf("%s@%s", r.Container.Name, r.ServerName)
289
}
290
291
// ReportKeyName returns the name of key on S3, Azure-Blob without extention
292
func (r ScanResult) ReportKeyName() (name string) {
293
	timestr := r.ScannedAt.Format(time.RFC3339)
294
	if len(r.Container.ContainerID) == 0 {
295
		return fmt.Sprintf("%s/%s", timestr, r.ServerName)
296
	}
297
	return fmt.Sprintf("%s/%s@%s", timestr, r.Container.Name, r.ServerName)
298
}
299
300
// ServerInfo returns server name one line
301
func (r ScanResult) ServerInfo() string {
302
	if len(r.Container.ContainerID) == 0 {
303
		return fmt.Sprintf("%s (%s%s)",
304
			r.FormatServerName(), r.Family, r.Release)
305
	}
306
	return fmt.Sprintf(
307
		"%s (%s%s) on %s",
308
		r.FormatServerName(),
309
		r.Family,
310
		r.Release,
311
		r.ServerName,
312
	)
313
}
314
315
// ServerInfoTui returns server information for TUI sidebar
316
func (r ScanResult) ServerInfoTui() string {
317
	if len(r.Container.ContainerID) == 0 {
318
		line := fmt.Sprintf("%s (%s%s)",
319
			r.ServerName, r.Family, r.Release)
320
		if r.RunningKernel.RebootRequired {
321
			return "[Reboot] " + line
322
		}
323
		return line
324
	}
325
326
	fmtstr := "|-- %s (%s%s)"
327
	if r.RunningKernel.RebootRequired {
328
		fmtstr = "|-- [Reboot] %s (%s%s)"
329
	}
330
	return fmt.Sprintf(fmtstr, r.Container.Name, r.Family, r.Release)
0 ignored issues
show
can't check non-constant format in call to Sprintf
Loading history...
331
}
332
333
// FormatServerName returns server and container name
334
func (r ScanResult) FormatServerName() (name string) {
335
	if len(r.Container.ContainerID) == 0 {
336
		name = r.ServerName
337
	} else {
338
		name = fmt.Sprintf("%s@%s",
339
			r.Container.Name, r.ServerName)
340
	}
341
	if r.RunningKernel.RebootRequired {
342
		name = "[Reboot Required] " + name
343
	}
344
	return
345
}
346
347
// FormatTextReportHeadedr returns header of text report
348
func (r ScanResult) FormatTextReportHeadedr() string {
349
	var buf bytes.Buffer
350
	for i := 0; i < len(r.ServerInfo()); i++ {
351
		buf.WriteString("=")
352
	}
353
354
	return fmt.Sprintf("%s\n%s\n%s, %s, %s, %s, %s\n",
355
		r.ServerInfo(),
356
		buf.String(),
357
		r.ScannedCves.FormatCveSummary(),
358
		r.ScannedCves.FormatFixedStatus(r.Packages),
359
		r.FormatUpdatablePacksSummary(),
360
		r.FormatExploitCveSummary(),
361
		r.FormatAlertSummary(),
362
	)
363
}
364
365
// FormatUpdatablePacksSummary returns a summary of updatable packages
366
func (r ScanResult) FormatUpdatablePacksSummary() string {
367
	if !r.isDisplayUpdatableNum() {
368
		return fmt.Sprintf("%d installed", len(r.Packages))
369
	}
370
371
	nUpdatable := 0
372
	for _, p := range r.Packages {
373
		if p.NewVersion == "" {
374
			continue
375
		}
376
		if p.Version != p.NewVersion || p.Release != p.NewRelease {
377
			nUpdatable++
378
		}
379
	}
380
	return fmt.Sprintf("%d installed, %d updatable",
381
		len(r.Packages),
382
		nUpdatable)
383
}
384
385
// FormatExploitCveSummary returns a summary of exploit cve
386
func (r ScanResult) FormatExploitCveSummary() string {
387
	nExploitCve := 0
388
	for _, vuln := range r.ScannedCves {
389
		if 0 < len(vuln.Exploits) {
390
			nExploitCve++
391
		}
392
	}
393
	return fmt.Sprintf("%d exploits", nExploitCve)
394
}
395
396
// FormatAlertSummary returns a summary of XCERT alerts
397
func (r ScanResult) FormatAlertSummary() string {
398
	jaCnt := 0
399
	enCnt := 0
400
	for _, vuln := range r.ScannedCves {
401
		if len(vuln.AlertDict.En) > 0 {
402
			enCnt += len(vuln.AlertDict.En)
403
		}
404
		if len(vuln.AlertDict.Ja) > 0 {
405
			jaCnt += len(vuln.AlertDict.Ja)
406
		}
407
	}
408
	return fmt.Sprintf("en: %d, ja: %d alerts", enCnt, jaCnt)
409
}
410
411
func (r ScanResult) isDisplayUpdatableNum() bool {
412
	var mode config.ScanMode
413
	s, _ := config.Conf.Servers[r.ServerName]
414
	mode = s.Mode
415
416
	if mode.IsOffline() {
417
		return false
418
	}
419
	if mode.IsFastRoot() || mode.IsDeep() {
420
		return true
421
	}
422
	if mode.IsFast() {
423
		switch r.Family {
424
		case config.RedHat,
425
			config.Oracle,
426
			config.Debian,
427
			config.Ubuntu,
428
			config.Raspbian:
429
			return false
430
		default:
431
			return true
432
		}
433
	}
434
	return false
435
}
436
437
// IsContainer returns whether this ServerInfo is about container
438
func (r ScanResult) IsContainer() bool {
439
	return 0 < len(r.Container.ContainerID)
440
}
441
442
// IsDeepScanMode checks if the scan mode is deep scan mode.
443
func (r ScanResult) IsDeepScanMode() bool {
444
	for _, s := range r.Config.Scan.Servers {
445
		for _, m := range s.ScanMode {
446
			if m == "deep" {
447
				return true
448
			}
449
		}
450
	}
451
	return false
452
}
453
454
// Container has Container information
455
type Container struct {
456
	ContainerID string `json:"containerID"`
457
	Name        string `json:"name"`
458
	Image       string `json:"image"`
459
	Type        string `json:"type"`
460
	UUID        string `json:"uuid"`
461
}
462
463
// Platform has platform information
464
type Platform struct {
465
	Name       string `json:"name"` // aws or azure or gcp or other...
466
	InstanceID string `json:"instanceID"`
467
}
468