Completed
Pull Request — master (#769)
by
unknown
09:35
created

scan.*base.getServerInfo   A

Complexity

Conditions 1

Size

Total Lines 2
Code Lines 2

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 1
eloc 2
dl 0
loc 2
rs 10
c 0
b 0
f 0
nop 0
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 scan
19
20
import (
21
	"bufio"
22
	"encoding/json"
23
	"fmt"
24
	"net"
25
	"regexp"
26
	"strings"
27
	"time"
28
29
	"github.com/future-architect/vuls/config"
30
	"github.com/future-architect/vuls/models"
31
	"github.com/hashicorp/go-version"
32
	"github.com/sirupsen/logrus"
33
)
34
35
type base struct {
36
	ServerInfo config.ServerInfo
37
	Distro     config.Distro
38
	Platform   models.Platform
39
	osPackages
40
41
	log  *logrus.Entry
42
	errs []error
43
}
44
45
//Command is for check dep
46
type Command struct {
47
	Command string
48
	Name    string
49
}
50
51
func (l *base) scanWp() (err error) {
52
	if len(l.ServerInfo.WpPath) == 0 && len(l.ServerInfo.WpToken) == 0 {
53
		return nil
54
	}
55
	if len(l.ServerInfo.WpPath) == 0 {
56
		err = fmt.Errorf("not found : WpPath")
57
		return
58
	}
59
	if len(l.ServerInfo.WpToken) == 0 {
60
		err = fmt.Errorf("not found : WpToken")
61
		return
62
	}
63
64
	cmd := []Command{{Command: "wp cli", Name: "wp"}, {Command: "curl --help", Name: "curl"}}
65
	for _, i := range cmd {
66
		if r := exec(l.ServerInfo, i.Command, noSudo); !r.isSuccess() {
67
			err = fmt.Errorf("%s command not installed", i.Name)
68
			return
69
		}
70
	}
71
72
	var unsecures []models.VulnInfo
73
	if unsecures, err = detectWp(l.ServerInfo); err != nil {
74
		l.log.Errorf("Failed to scan wordpress: %s", err)
75
		return err
76
	}
77
	l.WpVulnInfos = map[string]models.VulnInfo{}
78
	for _, i := range unsecures {
79
		l.WpVulnInfos[i.CveID] = i
80
	}
81
82
	return
83
}
84
85
//WpCveInfos is for wpvulndb's json
86
type WpCveInfos struct {
87
	ReleaseDate     string      `json:"release_date"`
88
	ChangelogURL    string      `json:"changelog_url"`
89
	Status          string      `json:"status"`
90
	LatestVersion   string      `json:"latest_version"`
91
	LastUpdated     string      `json:"last_updated"`
92
	Popular         bool        `json:"popular"`
93
	Vulnerabilities []WpCveInfo `json:"vulnerabilities"`
94
	Error           string      `json:"error"`
95
}
96
97
//WpCveInfo is for wpvulndb's json
98
type WpCveInfo struct {
99
	ID            int        `json:"id"`
100
	Title         string     `json:"title"`
101
	CreatedAt     string     `json:"created_at"`
102
	UpdatedAt     string     `json:"updated_at"`
103
	PublishedDate string     `json:"Published_date"`
104
	VulnType      string     `json:"Vuln_type"`
105
	References    References `json:"references"`
106
	FixedIn       string     `json:"fixed_in"`
107
}
108
109
//References is for wpvulndb's json
110
type References struct {
111
	URL     []string `json:"url"`
112
	Cve     []string `json:"cve"`
113
	Secunia []string `json:"secunia"`
114
}
115
116
func detectWp(c config.ServerInfo) (vinfos []models.VulnInfo, err error) {
117
118
	var coreVuln []models.VulnInfo
119
	if coreVuln, err = detectWpCore(c); err != nil {
120
		return
121
	}
122
	for _, i := range coreVuln {
123
		vinfos = append(vinfos, i)
124
	}
125
126
	var themeVuln []models.VulnInfo
127
	if themeVuln, err = detectWpTheme(c); err != nil {
128
		return
129
	}
130
	for _, i := range themeVuln {
131
		vinfos = append(vinfos, i)
132
	}
133
134
	var pluginVuln []models.VulnInfo
135
	if pluginVuln, err = detectWpPlugin(c); err != nil {
136
		return
137
	}
138
	for _, i := range pluginVuln {
139
		vinfos = append(vinfos, i)
140
	}
141
142
	return
143
}
144
145
func detectWpCore(c config.ServerInfo) (vinfos []models.VulnInfo, err error) {
146
	cmd := fmt.Sprintf("wp core version --path=%s", c.WpPath)
147
148
	var coreVersion string
149
	if r := exec(c, cmd, noSudo); r.isSuccess() {
150
		tmp := strings.Split(r.Stdout, ".")
151
		coreVersion = strings.Join(tmp, "")
152
		coreVersion = strings.TrimRight(coreVersion, "\r\n")
153
		if len(coreVersion) == 0 {
154
			return
155
		}
156
	}
157
	cmd = fmt.Sprintf("curl -k -H 'Authorization: Token token=%s' https://wpvulndb.com/api/v3/wordpresses/%s", c.WpToken, coreVersion)
158
	if r := exec(c, cmd, noSudo); r.isSuccess() {
159
		data := map[string]WpCveInfos{}
160
		if err = json.Unmarshal([]byte(r.Stdout), &data); err != nil {
161
			return
162
		}
163
		for _, i := range data {
164
			if len(i.Vulnerabilities) == 0 {
165
				continue
166
			}
167
			for _, e := range i.Vulnerabilities {
168
				if len(e.References.Cve) == 0 {
169
					continue
170
				}
171
				NotFixedYet := false
172
				if len(e.FixedIn) == 0 {
173
					NotFixedYet = true
174
				}
175
				var cveIDs []string
176
				for _, k := range e.References.Cve {
177
					cveIDs = append(cveIDs, "CVE-"+k)
178
				}
179
180
				for _, cveID := range cveIDs {
181
					vinfos = append(vinfos, models.VulnInfo{
182
						CveID: cveID,
183
						CveContents: models.NewCveContents(
184
							models.CveContent{
185
								CveID: cveID,
186
								Title: e.Title,
187
							},
188
						),
189
						AffectedPackages: models.PackageStatuses{
190
							{
191
								NotFixedYet: NotFixedYet,
192
							},
193
						},
194
					})
195
				}
196
			}
197
		}
198
	}
199
	return
200
}
201
202
//WpStatus is for wp command
203
type WpStatus struct {
204
	Name    string `json:"-"`
205
	Status  string `json:"-"`
206
	Update  string `json:"-"`
207
	Version string `json:"-"`
208
}
209
210
func detectWpTheme(c config.ServerInfo) (vinfos []models.VulnInfo, err error) {
211
	cmd := fmt.Sprintf("wp theme list --path=%s", c.WpPath)
212
213
	var themes []WpStatus
214
	if r := exec(c, cmd, noSudo); r.isSuccess() {
215
		themes = parseStatus(r.Stdout)
216
	}
217
218
	for _, theme := range themes {
219
		cmd := fmt.Sprintf("curl -H 'Authorization: Token token=%s' https://wpvulndb.com/api/v3/themes/%s", c.WpToken, theme.Name)
220
		if r := exec(c, cmd, noSudo); r.isSuccess() {
221
			data := map[string]WpCveInfos{}
222
			if err = json.Unmarshal([]byte(r.Stdout), &data); err != nil {
223
				var jsonError WpCveInfos
224
				if err = json.Unmarshal([]byte(r.Stdout), &jsonError); err != nil {
225
					return
226
				}
227
				continue
228
			}
229
			for _, i := range data {
230
				if len(i.Vulnerabilities) == 0 {
231
					continue
232
				}
233
				for _, e := range i.Vulnerabilities {
234
					if len(e.References.Cve) == 0 {
235
						continue
236
					}
237
					NotFixedYet := false
238
					if len(e.FixedIn) == 0 {
239
						NotFixedYet = true
240
					}
241
					if len(e.FixedIn) == 0 {
242
						e.FixedIn = "0"
243
					}
244
					var v1 *version.Version
245
					v1, err = version.NewVersion(theme.Version)
246
					if err != nil {
247
						return
248
					}
249
					var v2 *version.Version
250
					v2, err = version.NewVersion(e.FixedIn)
251
					if err != nil {
252
						return
253
					}
254
					if v1.LessThan(v2) {
255
						var cveIDs []string
256
						for _, k := range e.References.Cve {
257
							cveIDs = append(cveIDs, "CVE-"+k)
258
						}
259
260
						for _, cveID := range cveIDs {
261
							vinfos = append(vinfos, models.VulnInfo{
262
								CveID: cveID,
263
								CveContents: models.NewCveContents(
264
									models.CveContent{
265
										CveID: cveID,
266
										Title: e.Title,
267
									},
268
								),
269
								AffectedPackages: models.PackageStatuses{
270
									{
271
										NotFixedYet: NotFixedYet,
272
									},
273
								},
274
							})
275
						}
276
					}
277
				}
278
			}
279
280
		}
281
	}
282
	return
283
}
284
285
func detectWpPlugin(c config.ServerInfo) (vinfos []models.VulnInfo, err error) {
286
	cmd := fmt.Sprintf("wp plugin list --path=%s", c.WpPath)
287
288
	var plugins []WpStatus
289
	if r := exec(c, cmd, noSudo); r.isSuccess() {
290
		plugins = parseStatus(r.Stdout)
291
	}
292
293
	for _, plugin := range plugins {
294
		cmd := fmt.Sprintf("curl -H 'Authorization: Token token=%s' https://wpvulndb.com/api/v3/plugins/%s", c.WpToken, plugin.Name)
295
296
		if r := exec(c, cmd, noSudo); r.isSuccess() {
297
			data := map[string]WpCveInfos{}
298
			if err = json.Unmarshal([]byte(r.Stdout), &data); err != nil {
299
				var jsonError WpCveInfos
300
				if err = json.Unmarshal([]byte(r.Stdout), &jsonError); err != nil {
301
					return
302
				}
303
				continue
304
			}
305
			for _, i := range data {
306
				if len(i.Vulnerabilities) == 0 {
307
					continue
308
				}
309
				for _, e := range i.Vulnerabilities {
310
					if len(e.References.Cve) == 0 {
311
						continue
312
					}
313
					NotFixedYet := false
314
					if len(e.FixedIn) == 0 {
315
						NotFixedYet = true
316
					}
317
					if len(e.FixedIn) == 0 {
318
						e.FixedIn = "0"
319
					}
320
					var v1 *version.Version
321
					v1, err = version.NewVersion(plugin.Version)
322
					if err != nil {
323
						return
324
					}
325
					var v2 *version.Version
326
					v2, err = version.NewVersion(e.FixedIn)
327
					if err != nil {
328
						return
329
					}
330
					if v1.LessThan(v2) {
331
						var cveIDs []string
332
						for _, k := range e.References.Cve {
333
							cveIDs = append(cveIDs, "CVE-"+k)
334
						}
335
336
						for _, cveID := range cveIDs {
337
							vinfos = append(vinfos, models.VulnInfo{
338
								CveID: cveID,
339
								CveContents: models.NewCveContents(
340
									models.CveContent{
341
										CveID: cveID,
342
										Title: e.Title,
343
									},
344
								),
345
								AffectedPackages: models.PackageStatuses{
346
									{
347
										NotFixedYet: NotFixedYet,
348
									},
349
								},
350
							})
351
						}
352
					}
353
				}
354
			}
355
		}
356
	}
357
	return
358
}
359
360
func parseStatus(r string) (themes []WpStatus) {
361
	tmp := strings.Split(r, "\r\n")
362
	tmp = unset(tmp, 0)
363
	tmp = unset(tmp, 0)
364
	tmp = unset(tmp, 0)
365
	tmp = unset(tmp, len(tmp)-1)
366
	tmp = unset(tmp, len(tmp)-1)
367
	for _, k := range tmp {
368
		theme := strings.Split(k, "|")
369
		themes = append(themes, WpStatus{
370
			Name:    strings.TrimSpace(theme[1]),
371
			Status:  strings.TrimSpace(theme[2]),
372
			Update:  strings.TrimSpace(theme[3]),
373
			Version: strings.TrimSpace(theme[4]),
374
		})
375
	}
376
	return
377
}
378
379
func unset(s []string, i int) []string {
380
	if i >= len(s) {
381
		return s
382
	}
383
	return append(s[:i], s[i+1:]...)
384
}
385
386
func (l *base) wpConvertToModel() models.VulnInfos {
387
	return l.WpVulnInfos
388
}
389
390
func (l *base) exec(cmd string, sudo bool) execResult {
391
	return exec(l.ServerInfo, cmd, sudo, l.log)
392
}
393
394
func (l *base) setServerInfo(c config.ServerInfo) {
395
	l.ServerInfo = c
396
}
397
398
func (l *base) getServerInfo() config.ServerInfo {
399
	return l.ServerInfo
400
}
401
402
func (l *base) setDistro(fam, rel string) {
403
	d := config.Distro{
404
		Family:  fam,
405
		Release: rel,
406
	}
407
	l.Distro = d
408
409
	s := l.getServerInfo()
410
	s.Distro = d
411
	l.setServerInfo(s)
412
}
413
414
func (l *base) getDistro() config.Distro {
415
	return l.Distro
416
}
417
418
func (l *base) setPlatform(p models.Platform) {
419
	l.Platform = p
420
}
421
422
func (l *base) getPlatform() models.Platform {
423
	return l.Platform
424
}
425
426
func (l *base) runningKernel() (release, version string, err error) {
427
	r := l.exec("uname -r", noSudo)
428
	if !r.isSuccess() {
429
		return "", "", fmt.Errorf("Failed to SSH: %s", r)
430
	}
431
	release = strings.TrimSpace(r.Stdout)
432
433
	switch l.Distro.Family {
434
	case config.Debian:
435
		r := l.exec("uname -a", noSudo)
436
		if !r.isSuccess() {
437
			return "", "", fmt.Errorf("Failed to SSH: %s", r)
438
		}
439
		ss := strings.Fields(r.Stdout)
440
		if 6 < len(ss) {
441
			version = ss[6]
442
		}
443
	}
444
	return
445
}
446
447
func (l *base) allContainers() (containers []config.Container, err error) {
448
	switch l.ServerInfo.ContainerType {
449
	case "", "docker":
450
		stdout, err := l.dockerPs("-a --format '{{.ID}} {{.Names}} {{.Image}}'")
451
		if err != nil {
452
			return containers, err
453
		}
454
		return l.parseDockerPs(stdout)
455
	case "lxd":
456
		stdout, err := l.lxdPs("-c n")
457
		if err != nil {
458
			return containers, err
459
		}
460
		return l.parseLxdPs(stdout)
461
	case "lxc":
462
		stdout, err := l.lxcPs("-1")
463
		if err != nil {
464
			return containers, err
465
		}
466
		return l.parseLxcPs(stdout)
467
	default:
468
		return containers, fmt.Errorf(
469
			"Not supported yet: %s", l.ServerInfo.ContainerType)
470
	}
471
}
472
473
func (l *base) runningContainers() (containers []config.Container, err error) {
474
	switch l.ServerInfo.ContainerType {
475
	case "", "docker":
476
		stdout, err := l.dockerPs("--format '{{.ID}} {{.Names}} {{.Image}}'")
477
		if err != nil {
478
			return containers, err
479
		}
480
		return l.parseDockerPs(stdout)
481
	case "lxd":
482
		stdout, err := l.lxdPs("volatile.last_state.power=RUNNING -c n")
483
		if err != nil {
484
			return containers, err
485
		}
486
		return l.parseLxdPs(stdout)
487
	case "lxc":
488
		stdout, err := l.lxcPs("-1 --running")
489
		if err != nil {
490
			return containers, err
491
		}
492
		return l.parseLxcPs(stdout)
493
	default:
494
		return containers, fmt.Errorf(
495
			"Not supported yet: %s", l.ServerInfo.ContainerType)
496
	}
497
}
498
499
func (l *base) exitedContainers() (containers []config.Container, err error) {
500
	switch l.ServerInfo.ContainerType {
501
	case "", "docker":
502
		stdout, err := l.dockerPs("--filter 'status=exited' --format '{{.ID}} {{.Names}} {{.Image}}'")
503
		if err != nil {
504
			return containers, err
505
		}
506
		return l.parseDockerPs(stdout)
507
	case "lxd":
508
		stdout, err := l.lxdPs("volatile.last_state.power=STOPPED -c n")
509
		if err != nil {
510
			return containers, err
511
		}
512
		return l.parseLxdPs(stdout)
513
	case "lxc":
514
		stdout, err := l.lxcPs("-1 --stopped")
515
		if err != nil {
516
			return containers, err
517
		}
518
		return l.parseLxcPs(stdout)
519
	default:
520
		return containers, fmt.Errorf(
521
			"Not supported yet: %s", l.ServerInfo.ContainerType)
522
	}
523
}
524
525
func (l *base) dockerPs(option string) (string, error) {
526
	cmd := fmt.Sprintf("docker ps %s", option)
527
	r := l.exec(cmd, noSudo)
528
	if !r.isSuccess() {
529
		return "", fmt.Errorf("Failed to SSH: %s", r)
530
	}
531
	return r.Stdout, nil
532
}
533
534
func (l *base) lxdPs(option string) (string, error) {
535
	cmd := fmt.Sprintf("lxc list %s", option)
536
	r := l.exec(cmd, noSudo)
537
	if !r.isSuccess() {
538
		return "", fmt.Errorf("failed to SSH: %s", r)
539
	}
540
	return r.Stdout, nil
541
}
542
543
func (l *base) lxcPs(option string) (string, error) {
544
	cmd := fmt.Sprintf("lxc-ls %s 2>/dev/null", option)
545
	r := l.exec(cmd, sudo)
546
	if !r.isSuccess() {
547
		return "", fmt.Errorf("failed to SSH: %s", r)
548
	}
549
	return r.Stdout, nil
550
}
551
552
func (l *base) parseDockerPs(stdout string) (containers []config.Container, err error) {
553
	lines := strings.Split(stdout, "\n")
554
	for _, line := range lines {
555
		fields := strings.Fields(line)
556
		if len(fields) == 0 {
557
			break
558
		}
559
		if len(fields) != 3 {
560
			return containers, fmt.Errorf("Unknown format: %s", line)
561
		}
562
		containers = append(containers, config.Container{
563
			ContainerID: fields[0],
564
			Name:        fields[1],
565
			Image:       fields[2],
566
		})
567
	}
568
	return
569
}
570
571
func (l *base) parseLxdPs(stdout string) (containers []config.Container, err error) {
572
	lines := strings.Split(stdout, "\n")
573
	for i, line := range lines[3:] {
574
		if i%2 == 1 {
575
			continue
576
		}
577
		fields := strings.Fields(strings.Replace(line, "|", " ", -1))
578
		if len(fields) == 0 {
579
			break
580
		}
581
		if len(fields) != 1 {
582
			return containers, fmt.Errorf("Unknown format: %s", line)
583
		}
584
		containers = append(containers, config.Container{
585
			ContainerID: fields[0],
586
			Name:        fields[0],
587
		})
588
	}
589
	return
590
}
591
592
func (l *base) parseLxcPs(stdout string) (containers []config.Container, err error) {
593
	lines := strings.Split(stdout, "\n")
594
	for _, line := range lines {
595
		fields := strings.Fields(line)
596
		if len(fields) == 0 {
597
			break
598
		}
599
		containers = append(containers, config.Container{
600
			ContainerID: fields[0],
601
			Name:        fields[0],
602
		})
603
	}
604
	return
605
}
606
607
// ip executes ip command and returns IP addresses
608
func (l *base) ip() ([]string, []string, error) {
609
	// e.g.
610
	// 2: eth0: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1500 qdisc pfifo_fast state UP qlen 1000\    link/ether 52:54:00:2a:86:4c brd ff:ff:ff:ff:ff:ff
611
	// 2: eth0    inet 10.0.2.15/24 brd 10.0.2.255 scope global eth0
612
	// 2: eth0    inet6 fe80::5054:ff:fe2a:864c/64 scope link \       valid_lft forever preferred_lft forever
613
	r := l.exec("/sbin/ip -o addr", noSudo)
614
	if !r.isSuccess() {
615
		return nil, nil, fmt.Errorf("Failed to detect IP address: %v", r)
616
	}
617
	ipv4Addrs, ipv6Addrs := l.parseIP(r.Stdout)
618
	return ipv4Addrs, ipv6Addrs, nil
619
}
620
621
// parseIP parses the results of ip command
622
func (l *base) parseIP(stdout string) (ipv4Addrs []string, ipv6Addrs []string) {
623
	lines := strings.Split(stdout, "\n")
624
	for _, line := range lines {
625
		fields := strings.Fields(line)
626
		if len(fields) < 4 {
627
			continue
628
		}
629
		ip, _, err := net.ParseCIDR(fields[3])
630
		if err != nil {
631
			continue
632
		}
633
		if !ip.IsGlobalUnicast() {
634
			continue
635
		}
636
		if ipv4 := ip.To4(); ipv4 != nil {
637
			ipv4Addrs = append(ipv4Addrs, ipv4.String())
638
		} else {
639
			ipv6Addrs = append(ipv6Addrs, ip.String())
640
		}
641
	}
642
	return
643
}
644
645
func (l *base) detectPlatform() {
646
	if l.getServerInfo().Mode.IsOffline() {
647
		l.setPlatform(models.Platform{Name: "unknown"})
648
		return
649
	}
650
	ok, instanceID, err := l.detectRunningOnAws()
651
	if err != nil {
652
		l.setPlatform(models.Platform{Name: "other"})
653
		return
654
	}
655
	if ok {
656
		l.setPlatform(models.Platform{
657
			Name:       "aws",
658
			InstanceID: instanceID,
659
		})
660
		return
661
	}
662
663
	//TODO Azure, GCP...
664
	l.setPlatform(models.Platform{Name: "other"})
665
	return
666
}
667
668
func (l *base) detectRunningOnAws() (ok bool, instanceID string, err error) {
669
	if r := l.exec("type curl", noSudo); r.isSuccess() {
670
		cmd := "curl --max-time 1 --noproxy 169.254.169.254 http://169.254.169.254/latest/meta-data/instance-id"
671
		r := l.exec(cmd, noSudo)
672
		if r.isSuccess() {
673
			id := strings.TrimSpace(r.Stdout)
674
			if !l.isAwsInstanceID(id) {
675
				return false, "", nil
676
			}
677
			return true, id, nil
678
		}
679
680
		switch r.ExitStatus {
681
		case 28, 7:
682
			// Not running on AWS
683
			//  7   Failed to connect to host.
684
			// 28  operation timeout.
685
			return false, "", nil
686
		}
687
	}
688
689
	if r := l.exec("type wget", noSudo); r.isSuccess() {
690
		cmd := "wget --tries=3 --timeout=1 --no-proxy -q -O - http://169.254.169.254/latest/meta-data/instance-id"
691
		r := l.exec(cmd, noSudo)
692
		if r.isSuccess() {
693
			id := strings.TrimSpace(r.Stdout)
694
			if !l.isAwsInstanceID(id) {
695
				return false, "", nil
696
			}
697
			return true, id, nil
698
		}
699
700
		switch r.ExitStatus {
701
		case 4, 8:
702
			// Not running on AWS
703
			// 4   Network failure
704
			// 8   Server issued an error response.
705
			return false, "", nil
706
		}
707
	}
708
	return false, "", fmt.Errorf(
709
		"Failed to curl or wget to AWS instance metadata on %s. container: %s",
710
		l.ServerInfo.ServerName, l.ServerInfo.Container.Name)
711
}
712
713
// http://docs.aws.amazon.com/AWSEC2/latest/UserGuide/resource-ids.html
714
var awsInstanceIDPattern = regexp.MustCompile(`^i-[0-9a-f]+$`)
715
716
func (l *base) isAwsInstanceID(str string) bool {
717
	return awsInstanceIDPattern.MatchString(str)
718
}
719
720
func (l *base) convertToModel() models.ScanResult {
721
	ctype := l.ServerInfo.ContainerType
722
	if l.ServerInfo.Container.ContainerID != "" && ctype == "" {
723
		ctype = "docker"
724
	}
725
	container := models.Container{
726
		ContainerID: l.ServerInfo.Container.ContainerID,
727
		Name:        l.ServerInfo.Container.Name,
728
		Image:       l.ServerInfo.Container.Image,
729
		Type:        ctype,
730
	}
731
732
	errs := []string{}
733
	for _, e := range l.errs {
734
		errs = append(errs, fmt.Sprintf("%s", e))
735
	}
736
737
	return models.ScanResult{
738
		JSONVersion:   models.JSONVersion,
739
		ServerName:    l.ServerInfo.ServerName,
740
		ScannedAt:     time.Now(),
741
		ScanMode:      l.ServerInfo.Mode.String(),
742
		Family:        l.Distro.Family,
743
		Release:       l.Distro.Release,
744
		Container:     container,
745
		Platform:      l.Platform,
746
		IPv4Addrs:     l.ServerInfo.IPv4Addrs,
747
		IPv6Addrs:     l.ServerInfo.IPv6Addrs,
748
		ScannedCves:   l.VulnInfos,
749
		RunningKernel: l.Kernel,
750
		Packages:      l.Packages,
751
		SrcPackages:   l.SrcPackages,
752
		Optional:      l.ServerInfo.Optional,
753
		Errors:        errs,
754
	}
755
}
756
757
func (l *base) setErrs(errs []error) {
758
	l.errs = errs
759
}
760
761
func (l *base) getErrs() []error {
762
	return l.errs
763
}
764
765
const (
766
	systemd  = "systemd"
767
	upstart  = "upstart"
768
	sysVinit = "init"
769
)
770
771
// https://unix.stackexchange.com/questions/196166/how-to-find-out-if-a-system-uses-sysv-upstart-or-systemd-initsystem
772
func (l *base) detectInitSystem() (string, error) {
773
	var f func(string) (string, error)
774
	f = func(cmd string) (string, error) {
775
		r := l.exec(cmd, sudo)
776
		if !r.isSuccess() {
777
			return "", fmt.Errorf("Failed to stat %s: %s", cmd, r)
778
		}
779
		scanner := bufio.NewScanner(strings.NewReader(r.Stdout))
780
		scanner.Scan()
781
		line := strings.TrimSpace(scanner.Text())
782
		if strings.Contains(line, "systemd") {
783
			return systemd, nil
784
		} else if strings.Contains(line, "upstart") {
785
			return upstart, nil
786
		} else if strings.Contains(line, "File: ‘/proc/1/exe’ -> ‘/sbin/init’") ||
787
			strings.Contains(line, "File: `/proc/1/exe' -> `/sbin/init'") {
788
			return f("stat /sbin/init")
789
		} else if line == "File: ‘/sbin/init’" ||
790
			line == "File: `/sbin/init'" {
791
			r := l.exec("/sbin/init --version", noSudo)
792
			if r.isSuccess() {
793
				if strings.Contains(r.Stdout, "upstart") {
794
					return upstart, nil
795
				}
796
			}
797
			return sysVinit, nil
798
		}
799
		return "", fmt.Errorf("Failed to detect a init system: %s", line)
800
	}
801
	return f("stat /proc/1/exe")
802
}
803
804
func (l *base) detectServiceName(pid string) (string, error) {
805
	cmd := fmt.Sprintf("systemctl status --quiet --no-pager %s", pid)
806
	r := l.exec(cmd, noSudo)
807
	if !r.isSuccess() {
808
		return "", fmt.Errorf("Failed to stat %s: %s", cmd, r)
809
	}
810
	return l.parseSystemctlStatus(r.Stdout), nil
811
}
812
813
func (l *base) parseSystemctlStatus(stdout string) string {
814
	scanner := bufio.NewScanner(strings.NewReader(stdout))
815
	scanner.Scan()
816
	line := scanner.Text()
817
	ss := strings.Fields(line)
818
	if len(ss) < 2 || strings.HasPrefix(line, "Failed to get unit for PID") {
819
		return ""
820
	}
821
	return ss[1]
822
}
823