Completed
Pull Request — master (#769)
by
unknown
11:19
created

scan/base.go   F

Size/Duplication

Total Lines 758
Duplicated Lines 0 %

Importance

Changes 0
Metric Value
cc 183
eloc 503
dl 0
loc 758
rs 2
c 0
b 0
f 0

36 Methods

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