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

scan.*base.ip   A

Complexity

Conditions 2

Size

Total Lines 11
Code Lines 6

Duplication

Lines 0
Ratio 0 %

Importance

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