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

scan.detectWpCore   A

Complexity

Conditions 5

Size

Total Lines 24
Code Lines 18

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 5
eloc 18
dl 0
loc 24
rs 9.0333
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
	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{}, 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
			c.log.Infof("wordpress: %s not found", content.Name)
300
		} else {
301
			return nil, fmt.Errorf("status: %s", resp.Status)
302
		}
303
	}
304
	return body, nil
305
}
306
307
func contentConvertVinfos(stdout string, content WpStatus) (vinfos []models.VulnInfo, err error) {
308
	data := map[string]WpCveInfos{}
309
	if err = json.Unmarshal([]byte(stdout), &data); err != nil {
310
		var jsonError WpCveInfos
311
		if err = json.Unmarshal([]byte(stdout), &jsonError); err != nil {
312
			return nil, err
313
		}
314
	}
315
316
	for _, e := range data {
317
		if len(e.Vulnerabilities) == 0 {
318
			continue
319
		}
320
		for _, vulnerability := range e.Vulnerabilities {
321
			if len(vulnerability.References.Cve) == 0 {
322
				continue
323
			}
324
325
			var cveIDs []string
326
			for _, cveNumber := range vulnerability.References.Cve {
327
				cveIDs = append(cveIDs, "CVE-"+cveNumber)
328
			}
329
330
			if len(vulnerability.FixedIn) == 0 {
331
				for _, cveID := range cveIDs {
332
					vinfos = append(vinfos, models.VulnInfo{
333
						CveID: cveID,
334
						CveContents: models.NewCveContents(
335
							models.CveContent{
336
								CveID: cveID,
337
								Title: vulnerability.Title,
338
							},
339
						),
340
						AffectedPackages: models.PackageStatuses{
341
							{
342
								NotFixedYet: true,
343
							},
344
						},
345
					})
346
				}
347
				return vinfos, nil
348
			}
349
			var v1 *version.Version
350
			v1, err = version.NewVersion(content.Version)
351
			if err != nil {
352
				return nil, err
353
			}
354
			var v2 *version.Version
355
			v2, err = version.NewVersion(vulnerability.FixedIn)
356
			if err != nil {
357
				return nil, err
358
			}
359
			if v1.LessThan(v2) {
360
				for _, cveID := range cveIDs {
361
					vinfos = append(vinfos, models.VulnInfo{
362
						CveID: cveID,
363
						CveContents: models.NewCveContents(
364
							models.CveContent{
365
								CveID: cveID,
366
								Title: vulnerability.Title,
367
							},
368
						),
369
						AffectedPackages: models.PackageStatuses{
370
							{
371
								NotFixedYet: false,
372
							},
373
						},
374
					})
375
				}
376
			}
377
		}
378
	}
379
	return vinfos, nil
380
}
381
382
func (l *base) wpConvertToModel() models.VulnInfos {
383
	return l.WpVulnInfos
384
}
385
386
func (l *base) exec(cmd string, sudo bool) execResult {
387
	return exec(l.ServerInfo, cmd, sudo, l.log)
388
}
389
390
func (l *base) setServerInfo(c config.ServerInfo) {
391
	l.ServerInfo = c
392
}
393
394
func (l *base) getServerInfo() config.ServerInfo {
395
	return l.ServerInfo
396
}
397
398
func (l *base) setDistro(fam, rel string) {
399
	d := config.Distro{
400
		Family:  fam,
401
		Release: rel,
402
	}
403
	l.Distro = d
404
405
	s := l.getServerInfo()
406
	s.Distro = d
407
	l.setServerInfo(s)
408
}
409
410
func (l *base) getDistro() config.Distro {
411
	return l.Distro
412
}
413
414
func (l *base) setPlatform(p models.Platform) {
415
	l.Platform = p
416
}
417
418
func (l *base) getPlatform() models.Platform {
419
	return l.Platform
420
}
421
422
func (l *base) runningKernel() (release, version string, err error) {
423
	r := l.exec("uname -r", noSudo)
424
	if !r.isSuccess() {
425
		return "", "", fmt.Errorf("Failed to SSH: %s", r)
426
	}
427
	release = strings.TrimSpace(r.Stdout)
428
429
	switch l.Distro.Family {
430
	case config.Debian:
431
		r := l.exec("uname -a", noSudo)
432
		if !r.isSuccess() {
433
			return "", "", fmt.Errorf("Failed to SSH: %s", r)
434
		}
435
		ss := strings.Fields(r.Stdout)
436
		if 6 < len(ss) {
437
			version = ss[6]
438
		}
439
	}
440
	return
441
}
442
443
func (l *base) allContainers() (containers []config.Container, err error) {
444
	switch l.ServerInfo.ContainerType {
445
	case "", "docker":
446
		stdout, err := l.dockerPs("-a --format '{{.ID}} {{.Names}} {{.Image}}'")
447
		if err != nil {
448
			return containers, err
449
		}
450
		return l.parseDockerPs(stdout)
451
	case "lxd":
452
		stdout, err := l.lxdPs("-c n")
453
		if err != nil {
454
			return containers, err
455
		}
456
		return l.parseLxdPs(stdout)
457
	case "lxc":
458
		stdout, err := l.lxcPs("-1")
459
		if err != nil {
460
			return containers, err
461
		}
462
		return l.parseLxcPs(stdout)
463
	default:
464
		return containers, fmt.Errorf(
465
			"Not supported yet: %s", l.ServerInfo.ContainerType)
466
	}
467
}
468
469
func (l *base) runningContainers() (containers []config.Container, err error) {
470
	switch l.ServerInfo.ContainerType {
471
	case "", "docker":
472
		stdout, err := l.dockerPs("--format '{{.ID}} {{.Names}} {{.Image}}'")
473
		if err != nil {
474
			return containers, err
475
		}
476
		return l.parseDockerPs(stdout)
477
	case "lxd":
478
		stdout, err := l.lxdPs("volatile.last_state.power=RUNNING -c n")
479
		if err != nil {
480
			return containers, err
481
		}
482
		return l.parseLxdPs(stdout)
483
	case "lxc":
484
		stdout, err := l.lxcPs("-1 --running")
485
		if err != nil {
486
			return containers, err
487
		}
488
		return l.parseLxcPs(stdout)
489
	default:
490
		return containers, fmt.Errorf(
491
			"Not supported yet: %s", l.ServerInfo.ContainerType)
492
	}
493
}
494
495
func (l *base) exitedContainers() (containers []config.Container, err error) {
496
	switch l.ServerInfo.ContainerType {
497
	case "", "docker":
498
		stdout, err := l.dockerPs("--filter 'status=exited' --format '{{.ID}} {{.Names}} {{.Image}}'")
499
		if err != nil {
500
			return containers, err
501
		}
502
		return l.parseDockerPs(stdout)
503
	case "lxd":
504
		stdout, err := l.lxdPs("volatile.last_state.power=STOPPED -c n")
505
		if err != nil {
506
			return containers, err
507
		}
508
		return l.parseLxdPs(stdout)
509
	case "lxc":
510
		stdout, err := l.lxcPs("-1 --stopped")
511
		if err != nil {
512
			return containers, err
513
		}
514
		return l.parseLxcPs(stdout)
515
	default:
516
		return containers, fmt.Errorf(
517
			"Not supported yet: %s", l.ServerInfo.ContainerType)
518
	}
519
}
520
521
func (l *base) dockerPs(option string) (string, error) {
522
	cmd := fmt.Sprintf("docker ps %s", option)
523
	r := l.exec(cmd, noSudo)
524
	if !r.isSuccess() {
525
		return "", fmt.Errorf("Failed to SSH: %s", r)
526
	}
527
	return r.Stdout, nil
528
}
529
530
func (l *base) lxdPs(option string) (string, error) {
531
	cmd := fmt.Sprintf("lxc list %s", option)
532
	r := l.exec(cmd, noSudo)
533
	if !r.isSuccess() {
534
		return "", fmt.Errorf("failed to SSH: %s", r)
535
	}
536
	return r.Stdout, nil
537
}
538
539
func (l *base) lxcPs(option string) (string, error) {
540
	cmd := fmt.Sprintf("lxc-ls %s 2>/dev/null", option)
541
	r := l.exec(cmd, sudo)
542
	if !r.isSuccess() {
543
		return "", fmt.Errorf("failed to SSH: %s", r)
544
	}
545
	return r.Stdout, nil
546
}
547
548
func (l *base) parseDockerPs(stdout string) (containers []config.Container, err error) {
549
	lines := strings.Split(stdout, "\n")
550
	for _, line := range lines {
551
		fields := strings.Fields(line)
552
		if len(fields) == 0 {
553
			break
554
		}
555
		if len(fields) != 3 {
556
			return containers, fmt.Errorf("Unknown format: %s", line)
557
		}
558
		containers = append(containers, config.Container{
559
			ContainerID: fields[0],
560
			Name:        fields[1],
561
			Image:       fields[2],
562
		})
563
	}
564
	return
565
}
566
567
func (l *base) parseLxdPs(stdout string) (containers []config.Container, err error) {
568
	lines := strings.Split(stdout, "\n")
569
	for i, line := range lines[3:] {
570
		if i%2 == 1 {
571
			continue
572
		}
573
		fields := strings.Fields(strings.Replace(line, "|", " ", -1))
574
		if len(fields) == 0 {
575
			break
576
		}
577
		if len(fields) != 1 {
578
			return containers, fmt.Errorf("Unknown format: %s", line)
579
		}
580
		containers = append(containers, config.Container{
581
			ContainerID: fields[0],
582
			Name:        fields[0],
583
		})
584
	}
585
	return
586
}
587
588
func (l *base) parseLxcPs(stdout string) (containers []config.Container, err error) {
589
	lines := strings.Split(stdout, "\n")
590
	for _, line := range lines {
591
		fields := strings.Fields(line)
592
		if len(fields) == 0 {
593
			break
594
		}
595
		containers = append(containers, config.Container{
596
			ContainerID: fields[0],
597
			Name:        fields[0],
598
		})
599
	}
600
	return
601
}
602
603
// ip executes ip command and returns IP addresses
604
func (l *base) ip() ([]string, []string, error) {
605
	// e.g.
606
	// 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
607
	// 2: eth0    inet 10.0.2.15/24 brd 10.0.2.255 scope global eth0
608
	// 2: eth0    inet6 fe80::5054:ff:fe2a:864c/64 scope link \       valid_lft forever preferred_lft forever
609
	r := l.exec("/sbin/ip -o addr", noSudo)
610
	if !r.isSuccess() {
611
		return nil, nil, fmt.Errorf("Failed to detect IP address: %v", r)
612
	}
613
	ipv4Addrs, ipv6Addrs := l.parseIP(r.Stdout)
614
	return ipv4Addrs, ipv6Addrs, nil
615
}
616
617
// parseIP parses the results of ip command
618
func (l *base) parseIP(stdout string) (ipv4Addrs []string, ipv6Addrs []string) {
619
	lines := strings.Split(stdout, "\n")
620
	for _, line := range lines {
621
		fields := strings.Fields(line)
622
		if len(fields) < 4 {
623
			continue
624
		}
625
		ip, _, err := net.ParseCIDR(fields[3])
626
		if err != nil {
627
			continue
628
		}
629
		if !ip.IsGlobalUnicast() {
630
			continue
631
		}
632
		if ipv4 := ip.To4(); ipv4 != nil {
633
			ipv4Addrs = append(ipv4Addrs, ipv4.String())
634
		} else {
635
			ipv6Addrs = append(ipv6Addrs, ip.String())
636
		}
637
	}
638
	return
639
}
640
641
func (l *base) detectPlatform() {
642
	if l.getServerInfo().Mode.IsOffline() {
643
		l.setPlatform(models.Platform{Name: "unknown"})
644
		return
645
	}
646
	ok, instanceID, err := l.detectRunningOnAws()
647
	if err != nil {
648
		l.setPlatform(models.Platform{Name: "other"})
649
		return
650
	}
651
	if ok {
652
		l.setPlatform(models.Platform{
653
			Name:       "aws",
654
			InstanceID: instanceID,
655
		})
656
		return
657
	}
658
659
	//TODO Azure, GCP...
660
	l.setPlatform(models.Platform{Name: "other"})
661
	return
662
}
663
664
func (l *base) detectRunningOnAws() (ok bool, instanceID string, err error) {
665
	if r := l.exec("type curl", noSudo); r.isSuccess() {
666
		cmd := "curl --max-time 1 --noproxy 169.254.169.254 http://169.254.169.254/latest/meta-data/instance-id"
667
		r := l.exec(cmd, noSudo)
668
		if r.isSuccess() {
669
			id := strings.TrimSpace(r.Stdout)
670
			if !l.isAwsInstanceID(id) {
671
				return false, "", nil
672
			}
673
			return true, id, nil
674
		}
675
676
		switch r.ExitStatus {
677
		case 28, 7:
678
			// Not running on AWS
679
			//  7   Failed to connect to host.
680
			// 28  operation timeout.
681
			return false, "", nil
682
		}
683
	}
684
685
	if r := l.exec("type wget", noSudo); r.isSuccess() {
686
		cmd := "wget --tries=3 --timeout=1 --no-proxy -q -O - http://169.254.169.254/latest/meta-data/instance-id"
687
		r := l.exec(cmd, noSudo)
688
		if r.isSuccess() {
689
			id := strings.TrimSpace(r.Stdout)
690
			if !l.isAwsInstanceID(id) {
691
				return false, "", nil
692
			}
693
			return true, id, nil
694
		}
695
696
		switch r.ExitStatus {
697
		case 4, 8:
698
			// Not running on AWS
699
			// 4   Network failure
700
			// 8   Server issued an error response.
701
			return false, "", nil
702
		}
703
	}
704
	return false, "", fmt.Errorf(
705
		"Failed to curl or wget to AWS instance metadata on %s. container: %s",
706
		l.ServerInfo.ServerName, l.ServerInfo.Container.Name)
707
}
708
709
// http://docs.aws.amazon.com/AWSEC2/latest/UserGuide/resource-ids.html
710
var awsInstanceIDPattern = regexp.MustCompile(`^i-[0-9a-f]+$`)
711
712
func (l *base) isAwsInstanceID(str string) bool {
713
	return awsInstanceIDPattern.MatchString(str)
714
}
715
716
func (l *base) convertToModel() models.ScanResult {
717
	ctype := l.ServerInfo.ContainerType
718
	if l.ServerInfo.Container.ContainerID != "" && ctype == "" {
719
		ctype = "docker"
720
	}
721
	container := models.Container{
722
		ContainerID: l.ServerInfo.Container.ContainerID,
723
		Name:        l.ServerInfo.Container.Name,
724
		Image:       l.ServerInfo.Container.Image,
725
		Type:        ctype,
726
	}
727
728
	errs := []string{}
729
	for _, e := range l.errs {
730
		errs = append(errs, fmt.Sprintf("%s", e))
731
	}
732
733
	return models.ScanResult{
734
		JSONVersion:   models.JSONVersion,
735
		ServerName:    l.ServerInfo.ServerName,
736
		ScannedAt:     time.Now(),
737
		ScanMode:      l.ServerInfo.Mode.String(),
738
		Family:        l.Distro.Family,
739
		Release:       l.Distro.Release,
740
		Container:     container,
741
		Platform:      l.Platform,
742
		IPv4Addrs:     l.ServerInfo.IPv4Addrs,
743
		IPv6Addrs:     l.ServerInfo.IPv6Addrs,
744
		ScannedCves:   l.VulnInfos,
745
		RunningKernel: l.Kernel,
746
		Packages:      l.Packages,
747
		SrcPackages:   l.SrcPackages,
748
		Optional:      l.ServerInfo.Optional,
749
		Errors:        errs,
750
	}
751
}
752
753
func (l *base) setErrs(errs []error) {
754
	l.errs = errs
755
}
756
757
func (l *base) getErrs() []error {
758
	return l.errs
759
}
760
761
const (
762
	systemd  = "systemd"
763
	upstart  = "upstart"
764
	sysVinit = "init"
765
)
766
767
// https://unix.stackexchange.com/questions/196166/how-to-find-out-if-a-system-uses-sysv-upstart-or-systemd-initsystem
768
func (l *base) detectInitSystem() (string, error) {
769
	var f func(string) (string, error)
770
	f = func(cmd string) (string, error) {
771
		r := l.exec(cmd, sudo)
772
		if !r.isSuccess() {
773
			return "", fmt.Errorf("Failed to stat %s: %s", cmd, r)
774
		}
775
		scanner := bufio.NewScanner(strings.NewReader(r.Stdout))
776
		scanner.Scan()
777
		line := strings.TrimSpace(scanner.Text())
778
		if strings.Contains(line, "systemd") {
779
			return systemd, nil
780
		} else if strings.Contains(line, "upstart") {
781
			return upstart, nil
782
		} else if strings.Contains(line, "File: ‘/proc/1/exe’ -> ‘/sbin/init’") ||
783
			strings.Contains(line, "File: `/proc/1/exe' -> `/sbin/init'") {
784
			return f("stat /sbin/init")
785
		} else if line == "File: ‘/sbin/init’" ||
786
			line == "File: `/sbin/init'" {
787
			r := l.exec("/sbin/init --version", noSudo)
788
			if r.isSuccess() {
789
				if strings.Contains(r.Stdout, "upstart") {
790
					return upstart, nil
791
				}
792
			}
793
			return sysVinit, nil
794
		}
795
		return "", fmt.Errorf("Failed to detect a init system: %s", line)
796
	}
797
	return f("stat /proc/1/exe")
798
}
799
800
func (l *base) detectServiceName(pid string) (string, error) {
801
	cmd := fmt.Sprintf("systemctl status --quiet --no-pager %s", pid)
802
	r := l.exec(cmd, noSudo)
803
	if !r.isSuccess() {
804
		return "", fmt.Errorf("Failed to stat %s: %s", cmd, r)
805
	}
806
	return l.parseSystemctlStatus(r.Stdout), nil
807
}
808
809
func (l *base) parseSystemctlStatus(stdout string) string {
810
	scanner := bufio.NewScanner(strings.NewReader(stdout))
811
	scanner.Scan()
812
	line := scanner.Text()
813
	ss := strings.Fields(line)
814
	if len(ss) < 2 || strings.HasPrefix(line, "Failed to get unit for PID") {
815
		return ""
816
	}
817
	return ss[1]
818
}
819