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

scan.httpRequest   C

Complexity

Conditions 11

Size

Total Lines 38
Code Lines 31

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 11
eloc 31
dl 0
loc 38
rs 5.4
c 0
b 0
f 0
nop 3

How to fix   Complexity   

Complexity

Complex classes like scan.httpRequest often do a lot of different things. To break such a class down, we need to identify a cohesive component within that class. A common approach to find such a component is to look for fields/methods that share the same prefixes, or suffixes.

Once you have determined the fields that belong together, you can apply the Extract Class refactoring. If the component makes sense as a sub-class, Extract Subclass is also a candidate, and is often faster.

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