Completed
Pull Request — master (#769)
by
unknown
18:01 queued 08:29
created

scan.*base.allContainers   B

Complexity

Conditions 8

Size

Total Lines 23
Code Lines 20

Duplication

Lines 0
Ratio 0 %

Importance

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