Completed
Pull Request — master (#769)
by
unknown
20:20 queued 08:45
created

scan.detectWpCore   C

Complexity

Conditions 11

Size

Total Lines 48
Code Lines 38

Duplication

Lines 0
Ratio 0 %

Importance

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

How to fix   Complexity   

Complexity

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