Completed
Push — master ( 967c56...3d8753 )
by kota
07:11
created

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