1package scan 2 3import ( 4 "bufio" 5 "encoding/json" 6 "fmt" 7 "io/ioutil" 8 "net" 9 "os" 10 "regexp" 11 "strings" 12 "time" 13 14 "github.com/aquasecurity/fanal/analyzer" 15 16 "github.com/future-architect/vuls/config" 17 "github.com/future-architect/vuls/models" 18 "github.com/future-architect/vuls/util" 19 "github.com/sirupsen/logrus" 20 "golang.org/x/xerrors" 21 22 // Import library scanner 23 _ "github.com/aquasecurity/fanal/analyzer/library/bundler" 24 _ "github.com/aquasecurity/fanal/analyzer/library/cargo" 25 _ "github.com/aquasecurity/fanal/analyzer/library/composer" 26 _ "github.com/aquasecurity/fanal/analyzer/library/npm" 27 _ "github.com/aquasecurity/fanal/analyzer/library/pipenv" 28 _ "github.com/aquasecurity/fanal/analyzer/library/poetry" 29 _ "github.com/aquasecurity/fanal/analyzer/library/yarn" 30) 31 32type base struct { 33 ServerInfo config.ServerInfo 34 Distro config.Distro 35 Platform models.Platform 36 osPackages 37 LibraryScanners []models.LibraryScanner 38 WordPress *models.WordPressPackages 39 40 log *logrus.Entry 41 errs []error 42 warns []error 43} 44 45func (l *base) exec(cmd string, sudo bool) execResult { 46 return exec(l.ServerInfo, cmd, sudo, l.log) 47} 48 49func (l *base) setServerInfo(c config.ServerInfo) { 50 l.ServerInfo = c 51} 52 53func (l *base) getServerInfo() config.ServerInfo { 54 return l.ServerInfo 55} 56 57func (l *base) setDistro(fam, rel string) { 58 d := config.Distro{ 59 Family: fam, 60 Release: rel, 61 } 62 l.Distro = d 63 64 s := l.getServerInfo() 65 s.Distro = d 66 l.setServerInfo(s) 67} 68 69func (l *base) getDistro() config.Distro { 70 return l.Distro 71} 72 73func (l *base) setPlatform(p models.Platform) { 74 l.Platform = p 75} 76 77func (l *base) getPlatform() models.Platform { 78 return l.Platform 79} 80 81func (l *base) runningKernel() (release, version string, err error) { 82 r := l.exec("uname -r", noSudo) 83 if !r.isSuccess() { 84 return "", "", xerrors.Errorf("Failed to SSH: %s", r) 85 } 86 release = strings.TrimSpace(r.Stdout) 87 88 switch l.Distro.Family { 89 case config.Debian: 90 r := l.exec("uname -a", noSudo) 91 if !r.isSuccess() { 92 return "", "", xerrors.Errorf("Failed to SSH: %s", r) 93 } 94 ss := strings.Fields(r.Stdout) 95 if 6 < len(ss) { 96 version = ss[6] 97 } 98 } 99 return 100} 101 102func (l *base) allContainers() (containers []config.Container, err error) { 103 switch l.ServerInfo.ContainerType { 104 case "", "docker": 105 stdout, err := l.dockerPs("-a --format '{{.ID}} {{.Names}} {{.Image}}'") 106 if err != nil { 107 return containers, err 108 } 109 return l.parseDockerPs(stdout) 110 case "lxd": 111 stdout, err := l.lxdPs("-c n") 112 if err != nil { 113 return containers, err 114 } 115 return l.parseLxdPs(stdout) 116 case "lxc": 117 stdout, err := l.lxcPs("-1") 118 if err != nil { 119 return containers, err 120 } 121 return l.parseLxcPs(stdout) 122 default: 123 return containers, xerrors.Errorf( 124 "Not supported yet: %s", l.ServerInfo.ContainerType) 125 } 126} 127 128func (l *base) runningContainers() (containers []config.Container, err error) { 129 switch l.ServerInfo.ContainerType { 130 case "", "docker": 131 stdout, err := l.dockerPs("--format '{{.ID}} {{.Names}} {{.Image}}'") 132 if err != nil { 133 return containers, err 134 } 135 return l.parseDockerPs(stdout) 136 case "lxd": 137 stdout, err := l.lxdPs("volatile.last_state.power=RUNNING -c n") 138 if err != nil { 139 return containers, err 140 } 141 return l.parseLxdPs(stdout) 142 case "lxc": 143 stdout, err := l.lxcPs("-1 --running") 144 if err != nil { 145 return containers, err 146 } 147 return l.parseLxcPs(stdout) 148 default: 149 return containers, xerrors.Errorf( 150 "Not supported yet: %s", l.ServerInfo.ContainerType) 151 } 152} 153 154func (l *base) exitedContainers() (containers []config.Container, err error) { 155 switch l.ServerInfo.ContainerType { 156 case "", "docker": 157 stdout, err := l.dockerPs("--filter 'status=exited' --format '{{.ID}} {{.Names}} {{.Image}}'") 158 if err != nil { 159 return containers, err 160 } 161 return l.parseDockerPs(stdout) 162 case "lxd": 163 stdout, err := l.lxdPs("volatile.last_state.power=STOPPED -c n") 164 if err != nil { 165 return containers, err 166 } 167 return l.parseLxdPs(stdout) 168 case "lxc": 169 stdout, err := l.lxcPs("-1 --stopped") 170 if err != nil { 171 return containers, err 172 } 173 return l.parseLxcPs(stdout) 174 default: 175 return containers, xerrors.Errorf( 176 "Not supported yet: %s", l.ServerInfo.ContainerType) 177 } 178} 179 180func (l *base) dockerPs(option string) (string, error) { 181 cmd := fmt.Sprintf("docker ps %s", option) 182 r := l.exec(cmd, noSudo) 183 if !r.isSuccess() { 184 return "", xerrors.Errorf("Failed to SSH: %s", r) 185 } 186 return r.Stdout, nil 187} 188 189func (l *base) lxdPs(option string) (string, error) { 190 cmd := fmt.Sprintf("lxc list %s", option) 191 r := l.exec(cmd, noSudo) 192 if !r.isSuccess() { 193 return "", xerrors.Errorf("failed to SSH: %s", r) 194 } 195 return r.Stdout, nil 196} 197 198func (l *base) lxcPs(option string) (string, error) { 199 cmd := fmt.Sprintf("lxc-ls %s 2>/dev/null", option) 200 r := l.exec(cmd, sudo) 201 if !r.isSuccess() { 202 return "", xerrors.Errorf("failed to SSH: %s", r) 203 } 204 return r.Stdout, nil 205} 206 207func (l *base) parseDockerPs(stdout string) (containers []config.Container, err error) { 208 lines := strings.Split(stdout, "\n") 209 for _, line := range lines { 210 fields := strings.Fields(line) 211 if len(fields) == 0 { 212 break 213 } 214 if len(fields) != 3 { 215 return containers, xerrors.Errorf("Unknown format: %s", line) 216 } 217 containers = append(containers, config.Container{ 218 ContainerID: fields[0], 219 Name: fields[1], 220 Image: fields[2], 221 }) 222 } 223 return 224} 225 226func (l *base) parseLxdPs(stdout string) (containers []config.Container, err error) { 227 lines := strings.Split(stdout, "\n") 228 for i, line := range lines[3:] { 229 if i%2 == 1 { 230 continue 231 } 232 fields := strings.Fields(strings.Replace(line, "|", " ", -1)) 233 if len(fields) == 0 { 234 break 235 } 236 if len(fields) != 1 { 237 return containers, xerrors.Errorf("Unknown format: %s", line) 238 } 239 containers = append(containers, config.Container{ 240 ContainerID: fields[0], 241 Name: fields[0], 242 }) 243 } 244 return 245} 246 247func (l *base) parseLxcPs(stdout string) (containers []config.Container, err error) { 248 lines := strings.Split(stdout, "\n") 249 for _, line := range lines { 250 fields := strings.Fields(line) 251 if len(fields) == 0 { 252 break 253 } 254 containers = append(containers, config.Container{ 255 ContainerID: fields[0], 256 Name: fields[0], 257 }) 258 } 259 return 260} 261 262// ip executes ip command and returns IP addresses 263func (l *base) ip() ([]string, []string, error) { 264 // e.g. 265 // 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 266 // 2: eth0 inet 10.0.2.15/24 brd 10.0.2.255 scope global eth0 267 // 2: eth0 inet6 fe80::5054:ff:fe2a:864c/64 scope link \ valid_lft forever preferred_lft forever 268 r := l.exec("/sbin/ip -o addr", noSudo) 269 if !r.isSuccess() { 270 return nil, nil, xerrors.Errorf("Failed to detect IP address: %v", r) 271 } 272 ipv4Addrs, ipv6Addrs := l.parseIP(r.Stdout) 273 return ipv4Addrs, ipv6Addrs, nil 274} 275 276// parseIP parses the results of ip command 277func (l *base) parseIP(stdout string) (ipv4Addrs []string, ipv6Addrs []string) { 278 lines := strings.Split(stdout, "\n") 279 for _, line := range lines { 280 fields := strings.Fields(line) 281 if len(fields) < 4 { 282 continue 283 } 284 ip, _, err := net.ParseCIDR(fields[3]) 285 if err != nil { 286 continue 287 } 288 if !ip.IsGlobalUnicast() { 289 continue 290 } 291 if ipv4 := ip.To4(); ipv4 != nil { 292 ipv4Addrs = append(ipv4Addrs, ipv4.String()) 293 } else { 294 ipv6Addrs = append(ipv6Addrs, ip.String()) 295 } 296 } 297 return 298} 299 300func (l *base) detectPlatform() { 301 if l.getServerInfo().Mode.IsOffline() { 302 l.setPlatform(models.Platform{Name: "unknown"}) 303 return 304 } 305 ok, instanceID, err := l.detectRunningOnAws() 306 if err != nil { 307 l.setPlatform(models.Platform{Name: "other"}) 308 return 309 } 310 if ok { 311 l.setPlatform(models.Platform{ 312 Name: "aws", 313 InstanceID: instanceID, 314 }) 315 return 316 } 317 318 //TODO Azure, GCP... 319 l.setPlatform(models.Platform{Name: "other"}) 320 return 321} 322 323var dsFingerPrintPrefix = "AgentStatus.agentCertHash: " 324 325func (l *base) detectDeepSecurity() (fingerprint string, err error) { 326 // only work root user 327 if l.getServerInfo().Mode.IsFastRoot() { 328 if r := l.exec("test -f /opt/ds_agent/dsa_query", sudo); r.isSuccess() { 329 cmd := fmt.Sprintf(`/opt/ds_agent/dsa_query -c "GetAgentStatus" | grep %q`, dsFingerPrintPrefix) 330 r := l.exec(cmd, sudo) 331 if r.isSuccess() { 332 line := strings.TrimSpace(r.Stdout) 333 return line[len(dsFingerPrintPrefix):], nil 334 } 335 l.warns = append(l.warns, xerrors.New("Fail to retrieve deepsecurity fingerprint")) 336 } 337 } 338 return "", xerrors.Errorf("Failed to detect deepsecurity %s", l.ServerInfo.ServerName) 339} 340 341func (l *base) detectIPSs() { 342 if !config.Conf.DetectIPS { 343 return 344 } 345 346 ips := map[config.IPS]string{} 347 348 fingerprint, err := l.detectDeepSecurity() 349 if err != nil { 350 return 351 } 352 ips[config.DeepSecurity] = fingerprint 353 l.ServerInfo.IPSIdentifiers = ips 354} 355 356func (l *base) detectRunningOnAws() (ok bool, instanceID string, err error) { 357 if r := l.exec("type curl", noSudo); r.isSuccess() { 358 cmd := "curl --max-time 1 --noproxy 169.254.169.254 http://169.254.169.254/latest/meta-data/instance-id" 359 r := l.exec(cmd, noSudo) 360 if r.isSuccess() { 361 id := strings.TrimSpace(r.Stdout) 362 if !l.isAwsInstanceID(id) { 363 return false, "", nil 364 } 365 return true, id, nil 366 } 367 368 switch r.ExitStatus { 369 case 28, 7: 370 // Not running on AWS 371 // 7 Failed to connect to host. 372 // 28 operation timeout. 373 return false, "", nil 374 } 375 } 376 377 if r := l.exec("type wget", noSudo); r.isSuccess() { 378 cmd := "wget --tries=3 --timeout=1 --no-proxy -q -O - http://169.254.169.254/latest/meta-data/instance-id" 379 r := l.exec(cmd, noSudo) 380 if r.isSuccess() { 381 id := strings.TrimSpace(r.Stdout) 382 if !l.isAwsInstanceID(id) { 383 return false, "", nil 384 } 385 return true, id, nil 386 } 387 388 switch r.ExitStatus { 389 case 4, 8: 390 // Not running on AWS 391 // 4 Network failure 392 // 8 Server issued an error response. 393 return false, "", nil 394 } 395 } 396 return false, "", xerrors.Errorf( 397 "Failed to curl or wget to AWS instance metadata on %s. container: %s", 398 l.ServerInfo.ServerName, l.ServerInfo.Container.Name) 399} 400 401// http://docs.aws.amazon.com/AWSEC2/latest/UserGuide/resource-ids.html 402var awsInstanceIDPattern = regexp.MustCompile(`^i-[0-9a-f]+$`) 403 404func (l *base) isAwsInstanceID(str string) bool { 405 return awsInstanceIDPattern.MatchString(str) 406} 407 408func (l *base) convertToModel() models.ScanResult { 409 ctype := l.ServerInfo.ContainerType 410 if l.ServerInfo.Container.ContainerID != "" && ctype == "" { 411 ctype = "docker" 412 } 413 container := models.Container{ 414 ContainerID: l.ServerInfo.Container.ContainerID, 415 Name: l.ServerInfo.Container.Name, 416 Image: l.ServerInfo.Container.Image, 417 Type: ctype, 418 } 419 420 errs, warns := []string{}, []string{} 421 for _, e := range l.errs { 422 errs = append(errs, fmt.Sprintf("%+v", e)) 423 } 424 for _, w := range l.warns { 425 warns = append(warns, fmt.Sprintf("%+v", w)) 426 } 427 428 scannedVia := scannedViaRemote 429 if isLocalExec(l.ServerInfo.Port, l.ServerInfo.Host) { 430 scannedVia = scannedViaLocal 431 } else if l.ServerInfo.Type == config.ServerTypePseudo { 432 scannedVia = scannedViaPseudo 433 } 434 435 return models.ScanResult{ 436 JSONVersion: models.JSONVersion, 437 ServerName: l.ServerInfo.ServerName, 438 ScannedAt: time.Now(), 439 ScanMode: l.ServerInfo.Mode.String(), 440 Family: l.Distro.Family, 441 Release: l.Distro.Release, 442 Container: container, 443 Platform: l.Platform, 444 IPv4Addrs: l.ServerInfo.IPv4Addrs, 445 IPv6Addrs: l.ServerInfo.IPv6Addrs, 446 IPSIdentifiers: l.ServerInfo.IPSIdentifiers, 447 ScannedCves: l.VulnInfos, 448 ScannedVia: scannedVia, 449 RunningKernel: l.Kernel, 450 Packages: l.Packages, 451 SrcPackages: l.SrcPackages, 452 WordPressPackages: l.WordPress, 453 LibraryScanners: l.LibraryScanners, 454 Optional: l.ServerInfo.Optional, 455 Errors: errs, 456 Warnings: warns, 457 } 458} 459 460func (l *base) setErrs(errs []error) { 461 l.errs = errs 462} 463 464func (l *base) getErrs() []error { 465 return l.errs 466} 467 468const ( 469 systemd = "systemd" 470 upstart = "upstart" 471 sysVinit = "init" 472) 473 474// https://unix.stackexchange.com/questions/196166/how-to-find-out-if-a-system-uses-sysv-upstart-or-systemd-initsystem 475func (l *base) detectInitSystem() (string, error) { 476 var f func(string) (string, error) 477 f = func(cmd string) (string, error) { 478 r := l.exec(cmd, sudo) 479 if !r.isSuccess() { 480 return "", xerrors.Errorf("Failed to stat %s: %s", cmd, r) 481 } 482 scanner := bufio.NewScanner(strings.NewReader(r.Stdout)) 483 scanner.Scan() 484 line := strings.TrimSpace(scanner.Text()) 485 if strings.Contains(line, "systemd") { 486 return systemd, nil 487 } else if strings.Contains(line, "upstart") { 488 return upstart, nil 489 } else if strings.Contains(line, "File: ‘/proc/1/exe’ -> ‘/sbin/init’") || 490 strings.Contains(line, "File: `/proc/1/exe' -> `/sbin/init'") { 491 return f("stat /sbin/init") 492 } else if line == "File: ‘/sbin/init’" || 493 line == "File: `/sbin/init'" { 494 r := l.exec("/sbin/init --version", noSudo) 495 if r.isSuccess() { 496 if strings.Contains(r.Stdout, "upstart") { 497 return upstart, nil 498 } 499 } 500 return sysVinit, nil 501 } 502 return "", xerrors.Errorf("Failed to detect a init system: %s", line) 503 } 504 return f("stat /proc/1/exe") 505} 506 507func (l *base) detectServiceName(pid string) (string, error) { 508 cmd := fmt.Sprintf("systemctl status --quiet --no-pager %s", pid) 509 r := l.exec(cmd, noSudo) 510 if !r.isSuccess() { 511 return "", xerrors.Errorf("Failed to stat %s: %s", cmd, r) 512 } 513 return l.parseSystemctlStatus(r.Stdout), nil 514} 515 516func (l *base) parseSystemctlStatus(stdout string) string { 517 scanner := bufio.NewScanner(strings.NewReader(stdout)) 518 scanner.Scan() 519 line := scanner.Text() 520 ss := strings.Fields(line) 521 if len(ss) < 2 || strings.HasPrefix(line, "Failed to get unit for PID") { 522 return "" 523 } 524 return ss[1] 525} 526 527func (l *base) scanLibraries() (err error) { 528 // image already detected libraries 529 if len(l.LibraryScanners) != 0 { 530 return nil 531 } 532 533 // library scan for servers need lockfiles 534 if len(l.ServerInfo.Lockfiles) == 0 && !l.ServerInfo.FindLock { 535 return nil 536 } 537 538 libFilemap := map[string][]byte{} 539 detectFiles := l.ServerInfo.Lockfiles 540 541 // auto detect lockfile 542 if l.ServerInfo.FindLock { 543 findopt := "" 544 for filename := range models.LibraryMap { 545 findopt += fmt.Sprintf("-name %q -o ", "*"+filename) 546 } 547 548 // delete last "-o " 549 // find / -name "*package-lock.json" -o -name "*yarn.lock" ... 2>&1 | grep -v "find: " 550 cmd := fmt.Sprintf(`find / ` + findopt[:len(findopt)-3] + ` 2>&1 | grep -v "find: "`) 551 r := exec(l.ServerInfo, cmd, noSudo) 552 if r.ExitStatus != 0 && r.ExitStatus != 1 { 553 return xerrors.Errorf("Failed to find lock files") 554 } 555 detectFiles = append(detectFiles, strings.Split(r.Stdout, "\n")...) 556 } 557 558 for _, path := range detectFiles { 559 if path == "" { 560 continue 561 } 562 // skip already exist 563 if _, ok := libFilemap[path]; ok { 564 continue 565 } 566 567 var bytes []byte 568 switch l.Distro.Family { 569 case config.ServerTypePseudo: 570 bytes, err = ioutil.ReadFile(path) 571 if err != nil { 572 return xerrors.Errorf("Failed to get target file: %s, filepath: %s", err, path) 573 } 574 default: 575 cmd := fmt.Sprintf("cat %s", path) 576 r := exec(l.ServerInfo, cmd, noSudo) 577 if !r.isSuccess() { 578 return xerrors.Errorf("Failed to get target file: %s, filepath: %s", r, path) 579 } 580 bytes = []byte(r.Stdout) 581 } 582 libFilemap[path] = bytes 583 } 584 585 for path, b := range libFilemap { 586 res, err := analyzer.AnalyzeFile(path, &DummyFileInfo{}, func() ([]byte, error) { 587 return b, nil 588 }) 589 if err != nil { 590 return xerrors.Errorf("Failed to get libs: %w", err) 591 } 592 libscan, err := convertLibWithScanner(res.Applications) 593 if err != nil { 594 return xerrors.Errorf("Failed to scan libraries: %w", err) 595 } 596 l.LibraryScanners = append(l.LibraryScanners, libscan...) 597 } 598 return nil 599} 600 601type DummyFileInfo struct{} 602 603func (d *DummyFileInfo) Name() string { return "dummy" } 604func (d *DummyFileInfo) Size() int64 { return 0 } 605func (d *DummyFileInfo) Mode() os.FileMode { return 0 } 606func (d *DummyFileInfo) ModTime() time.Time { return time.Now() } 607func (d *DummyFileInfo) IsDir() bool { return false } 608func (d *DummyFileInfo) Sys() interface{} { return nil } 609 610func (l *base) scanWordPress() (err error) { 611 wpOpts := []string{l.ServerInfo.WordPress.OSUser, 612 l.ServerInfo.WordPress.DocRoot, 613 l.ServerInfo.WordPress.CmdPath, 614 l.ServerInfo.WordPress.WPVulnDBToken, 615 } 616 var isScanWp, hasEmptyOpt bool 617 for _, opt := range wpOpts { 618 if opt != "" { 619 isScanWp = true 620 break 621 } else { 622 hasEmptyOpt = true 623 } 624 } 625 if !isScanWp { 626 return nil 627 } 628 629 if hasEmptyOpt { 630 return xerrors.Errorf("%s has empty WordPress opts: %s", 631 l.getServerInfo().GetServerName(), wpOpts) 632 } 633 634 cmd := fmt.Sprintf("sudo -u %s -i -- %s cli version --allow-root", 635 l.ServerInfo.WordPress.OSUser, 636 l.ServerInfo.WordPress.CmdPath) 637 if r := exec(l.ServerInfo, cmd, noSudo); !r.isSuccess() { 638 l.ServerInfo.WordPress.WPVulnDBToken = "secret" 639 return xerrors.Errorf("Failed to exec `%s`. Check the OS user, command path of wp-cli, DocRoot and permission: %#v", cmd, l.ServerInfo.WordPress) 640 } 641 642 wp, err := l.detectWordPress() 643 if err != nil { 644 return xerrors.Errorf("Failed to scan wordpress: %w", err) 645 } 646 l.WordPress = wp 647 return nil 648} 649 650func (l *base) detectWordPress() (*models.WordPressPackages, error) { 651 ver, err := l.detectWpCore() 652 if err != nil { 653 return nil, err 654 } 655 656 themes, err := l.detectWpThemes() 657 if err != nil { 658 return nil, err 659 } 660 661 plugins, err := l.detectWpPlugins() 662 if err != nil { 663 return nil, err 664 } 665 666 pkgs := models.WordPressPackages{ 667 models.WpPackage{ 668 Name: models.WPCore, 669 Version: ver, 670 Type: models.WPCore, 671 }, 672 } 673 pkgs = append(pkgs, themes...) 674 pkgs = append(pkgs, plugins...) 675 return &pkgs, nil 676} 677 678func (l *base) detectWpCore() (string, error) { 679 cmd := fmt.Sprintf("sudo -u %s -i -- %s core version --path=%s --allow-root", 680 l.ServerInfo.WordPress.OSUser, 681 l.ServerInfo.WordPress.CmdPath, 682 l.ServerInfo.WordPress.DocRoot) 683 684 r := exec(l.ServerInfo, cmd, noSudo) 685 if !r.isSuccess() { 686 return "", xerrors.Errorf("Failed to get wp core version: %s", r) 687 } 688 return strings.TrimSpace(r.Stdout), nil 689} 690 691func (l *base) detectWpThemes() ([]models.WpPackage, error) { 692 cmd := fmt.Sprintf("sudo -u %s -i -- %s theme list --path=%s --format=json --allow-root 2>/dev/null", 693 l.ServerInfo.WordPress.OSUser, 694 l.ServerInfo.WordPress.CmdPath, 695 l.ServerInfo.WordPress.DocRoot) 696 697 var themes []models.WpPackage 698 r := exec(l.ServerInfo, cmd, noSudo) 699 if !r.isSuccess() { 700 return nil, xerrors.Errorf("Failed to get a list of WordPress plugins: %s", r) 701 } 702 err := json.Unmarshal([]byte(r.Stdout), &themes) 703 if err != nil { 704 return nil, xerrors.Errorf("Failed to unmarshal wp theme list: %w", err) 705 } 706 for i := range themes { 707 themes[i].Type = models.WPTheme 708 } 709 return themes, nil 710} 711 712func (l *base) detectWpPlugins() ([]models.WpPackage, error) { 713 cmd := fmt.Sprintf("sudo -u %s -i -- %s plugin list --path=%s --format=json --allow-root 2>/dev/null", 714 l.ServerInfo.WordPress.OSUser, 715 l.ServerInfo.WordPress.CmdPath, 716 l.ServerInfo.WordPress.DocRoot) 717 718 var plugins []models.WpPackage 719 r := exec(l.ServerInfo, cmd, noSudo) 720 if !r.isSuccess() { 721 return nil, xerrors.Errorf("Failed to wp plugin list: %s", r) 722 } 723 if err := json.Unmarshal([]byte(r.Stdout), &plugins); err != nil { 724 return nil, err 725 } 726 for i := range plugins { 727 plugins[i].Type = models.WPPlugin 728 } 729 return plugins, nil 730} 731 732func (l *base) scanPorts() (err error) { 733 dest := l.detectScanDest() 734 open, err := l.execPortsScan(dest) 735 if err != nil { 736 return err 737 } 738 l.updatePortStatus(open) 739 740 return nil 741} 742 743func (l *base) detectScanDest() map[string][]string { 744 scanIPPortsMap := map[string][]string{} 745 746 for _, p := range l.osPackages.Packages { 747 if p.AffectedProcs == nil { 748 continue 749 } 750 for _, proc := range p.AffectedProcs { 751 if proc.ListenPortStats == nil { 752 continue 753 } 754 for _, port := range proc.ListenPortStats { 755 scanIPPortsMap[port.BindAddress] = append(scanIPPortsMap[port.BindAddress], port.Port) 756 } 757 } 758 } 759 760 scanDestIPPorts := map[string][]string{} 761 for addr, ports := range scanIPPortsMap { 762 if addr == "*" { 763 for _, addr := range l.ServerInfo.IPv4Addrs { 764 scanDestIPPorts[addr] = append(scanDestIPPorts[addr], ports...) 765 } 766 } else { 767 scanDestIPPorts[addr] = append(scanDestIPPorts[addr], ports...) 768 } 769 } 770 771 uniqScanDestIPPorts := map[string][]string{} 772 for i, scanDest := range scanDestIPPorts { 773 m := map[string]bool{} 774 for _, e := range scanDest { 775 if !m[e] { 776 m[e] = true 777 uniqScanDestIPPorts[i] = append(uniqScanDestIPPorts[i], e) 778 } 779 } 780 } 781 782 return uniqScanDestIPPorts 783} 784 785func (l *base) execPortsScan(scanDestIPPorts map[string][]string) ([]string, error) { 786 listenIPPorts := []string{} 787 788 for ip, ports := range scanDestIPPorts { 789 if !isLocalExec(l.ServerInfo.Port, l.ServerInfo.Host) && net.ParseIP(ip).IsLoopback() { 790 continue 791 } 792 for _, port := range ports { 793 scanDest := ip + ":" + port 794 conn, err := net.DialTimeout("tcp", scanDest, time.Duration(1)*time.Second) 795 if err != nil { 796 continue 797 } 798 conn.Close() 799 listenIPPorts = append(listenIPPorts, scanDest) 800 } 801 } 802 803 return listenIPPorts, nil 804} 805 806func (l *base) updatePortStatus(listenIPPorts []string) { 807 for name, p := range l.osPackages.Packages { 808 if p.AffectedProcs == nil { 809 continue 810 } 811 for i, proc := range p.AffectedProcs { 812 if proc.ListenPortStats == nil { 813 continue 814 } 815 for j, port := range proc.ListenPortStats { 816 l.osPackages.Packages[name].AffectedProcs[i].ListenPortStats[j].PortReachableTo = l.findPortTestSuccessOn(listenIPPorts, port) 817 } 818 } 819 } 820} 821 822func (l *base) findPortTestSuccessOn(listenIPPorts []string, searchListenPort models.PortStat) []string { 823 addrs := []string{} 824 825 for _, ipPort := range listenIPPorts { 826 ipPort, err := models.NewPortStat(ipPort) 827 if err != nil { 828 util.Log.Warnf("Failed to find: %+v", err) 829 continue 830 } 831 if searchListenPort.BindAddress == "*" { 832 if searchListenPort.Port == ipPort.Port { 833 addrs = append(addrs, ipPort.BindAddress) 834 } 835 } else if searchListenPort.BindAddress == ipPort.BindAddress && searchListenPort.Port == ipPort.Port { 836 addrs = append(addrs, ipPort.BindAddress) 837 } 838 } 839 840 return addrs 841} 842 843func (l *base) ps() (stdout string, err error) { 844 cmd := `LANGUAGE=en_US.UTF-8 ps --no-headers --ppid 2 -p 2 --deselect -o pid,comm` 845 r := l.exec(util.PrependProxyEnv(cmd), noSudo) 846 if !r.isSuccess() { 847 return "", xerrors.Errorf("Failed to SSH: %s", r) 848 } 849 return r.Stdout, nil 850} 851 852func (l *base) parsePs(stdout string) map[string]string { 853 pidNames := map[string]string{} 854 scanner := bufio.NewScanner(strings.NewReader(stdout)) 855 for scanner.Scan() { 856 line := strings.TrimSpace(scanner.Text()) 857 ss := strings.Fields(line) 858 if len(ss) < 2 { 859 continue 860 } 861 pidNames[ss[0]] = ss[1] 862 } 863 return pidNames 864} 865 866func (l *base) lsProcExe(pid string) (stdout string, err error) { 867 cmd := fmt.Sprintf("ls -l /proc/%s/exe", pid) 868 r := l.exec(util.PrependProxyEnv(cmd), sudo) 869 if !r.isSuccess() { 870 return "", xerrors.Errorf("Failed to SSH: %s", r) 871 } 872 return r.Stdout, nil 873} 874 875func (l *base) parseLsProcExe(stdout string) (string, error) { 876 ss := strings.Fields(stdout) 877 if len(ss) < 11 { 878 return "", xerrors.Errorf("Unknown format: %s", stdout) 879 } 880 return ss[10], nil 881} 882 883func (l *base) grepProcMap(pid string) (stdout string, err error) { 884 cmd := fmt.Sprintf(`cat /proc/%s/maps 2>/dev/null | grep -v " 00:00 " | awk '{print $6}' | sort -n | uniq`, pid) 885 r := l.exec(util.PrependProxyEnv(cmd), sudo) 886 if !r.isSuccess() { 887 return "", xerrors.Errorf("Failed to SSH: %s", r) 888 } 889 return r.Stdout, nil 890} 891 892func (l *base) parseGrepProcMap(stdout string) (soPaths []string) { 893 scanner := bufio.NewScanner(strings.NewReader(stdout)) 894 for scanner.Scan() { 895 line := strings.TrimSpace(scanner.Text()) 896 soPaths = append(soPaths, line) 897 } 898 return soPaths 899} 900 901func (l *base) lsOfListen() (stdout string, err error) { 902 cmd := `lsof -i -P -n | grep LISTEN` 903 r := l.exec(util.PrependProxyEnv(cmd), sudo) 904 if !r.isSuccess(0, 1) { 905 return "", xerrors.Errorf("Failed to lsof: %s", r) 906 } 907 return r.Stdout, nil 908} 909 910func (l *base) parseLsOf(stdout string) map[string][]string { 911 portPids := map[string][]string{} 912 scanner := bufio.NewScanner(strings.NewReader(stdout)) 913 for scanner.Scan() { 914 ss := strings.Fields(scanner.Text()) 915 if len(ss) < 10 { 916 continue 917 } 918 pid, ipPort := ss[1], ss[8] 919 portPids[ipPort] = util.AppendIfMissing(portPids[ipPort], pid) 920 } 921 return portPids 922} 923