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

scan.*base.parseLxcPs   A

Complexity

Conditions 4

Size

Total Lines 13
Code Lines 10

Duplication

Lines 0
Ratio 0 %

Importance

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