feat(scan): WordPress Vulnerability Scan (core, plugin, theme) (#769)
https://github.com/future-architect/vuls/pull/769
This commit is contained in:
188
scan/base.go
188
scan/base.go
@@ -19,6 +19,7 @@ package scan
|
||||
|
||||
import (
|
||||
"bufio"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"net"
|
||||
"regexp"
|
||||
@@ -28,6 +29,7 @@ import (
|
||||
"github.com/future-architect/vuls/config"
|
||||
"github.com/future-architect/vuls/models"
|
||||
"github.com/sirupsen/logrus"
|
||||
"golang.org/x/xerrors"
|
||||
)
|
||||
|
||||
type base struct {
|
||||
@@ -35,6 +37,7 @@ type base struct {
|
||||
Distro config.Distro
|
||||
Platform models.Platform
|
||||
osPackages
|
||||
WordPress *models.WordPressPackages
|
||||
|
||||
log *logrus.Entry
|
||||
errs []error
|
||||
@@ -79,7 +82,7 @@ func (l *base) getPlatform() models.Platform {
|
||||
func (l *base) runningKernel() (release, version string, err error) {
|
||||
r := l.exec("uname -r", noSudo)
|
||||
if !r.isSuccess() {
|
||||
return "", "", fmt.Errorf("Failed to SSH: %s", r)
|
||||
return "", "", xerrors.Errorf("Failed to SSH: %s", r)
|
||||
}
|
||||
release = strings.TrimSpace(r.Stdout)
|
||||
|
||||
@@ -87,7 +90,7 @@ func (l *base) runningKernel() (release, version string, err error) {
|
||||
case config.Debian:
|
||||
r := l.exec("uname -a", noSudo)
|
||||
if !r.isSuccess() {
|
||||
return "", "", fmt.Errorf("Failed to SSH: %s", r)
|
||||
return "", "", xerrors.Errorf("Failed to SSH: %s", r)
|
||||
}
|
||||
ss := strings.Fields(r.Stdout)
|
||||
if 6 < len(ss) {
|
||||
@@ -118,7 +121,7 @@ func (l *base) allContainers() (containers []config.Container, err error) {
|
||||
}
|
||||
return l.parseLxcPs(stdout)
|
||||
default:
|
||||
return containers, fmt.Errorf(
|
||||
return containers, xerrors.Errorf(
|
||||
"Not supported yet: %s", l.ServerInfo.ContainerType)
|
||||
}
|
||||
}
|
||||
@@ -144,7 +147,7 @@ func (l *base) runningContainers() (containers []config.Container, err error) {
|
||||
}
|
||||
return l.parseLxcPs(stdout)
|
||||
default:
|
||||
return containers, fmt.Errorf(
|
||||
return containers, xerrors.Errorf(
|
||||
"Not supported yet: %s", l.ServerInfo.ContainerType)
|
||||
}
|
||||
}
|
||||
@@ -170,7 +173,7 @@ func (l *base) exitedContainers() (containers []config.Container, err error) {
|
||||
}
|
||||
return l.parseLxcPs(stdout)
|
||||
default:
|
||||
return containers, fmt.Errorf(
|
||||
return containers, xerrors.Errorf(
|
||||
"Not supported yet: %s", l.ServerInfo.ContainerType)
|
||||
}
|
||||
}
|
||||
@@ -179,7 +182,7 @@ func (l *base) dockerPs(option string) (string, error) {
|
||||
cmd := fmt.Sprintf("docker ps %s", option)
|
||||
r := l.exec(cmd, noSudo)
|
||||
if !r.isSuccess() {
|
||||
return "", fmt.Errorf("Failed to SSH: %s", r)
|
||||
return "", xerrors.Errorf("Failed to SSH: %s", r)
|
||||
}
|
||||
return r.Stdout, nil
|
||||
}
|
||||
@@ -188,7 +191,7 @@ func (l *base) lxdPs(option string) (string, error) {
|
||||
cmd := fmt.Sprintf("lxc list %s", option)
|
||||
r := l.exec(cmd, noSudo)
|
||||
if !r.isSuccess() {
|
||||
return "", fmt.Errorf("failed to SSH: %s", r)
|
||||
return "", xerrors.Errorf("failed to SSH: %s", r)
|
||||
}
|
||||
return r.Stdout, nil
|
||||
}
|
||||
@@ -197,7 +200,7 @@ func (l *base) lxcPs(option string) (string, error) {
|
||||
cmd := fmt.Sprintf("lxc-ls %s 2>/dev/null", option)
|
||||
r := l.exec(cmd, sudo)
|
||||
if !r.isSuccess() {
|
||||
return "", fmt.Errorf("failed to SSH: %s", r)
|
||||
return "", xerrors.Errorf("failed to SSH: %s", r)
|
||||
}
|
||||
return r.Stdout, nil
|
||||
}
|
||||
@@ -210,7 +213,7 @@ func (l *base) parseDockerPs(stdout string) (containers []config.Container, err
|
||||
break
|
||||
}
|
||||
if len(fields) != 3 {
|
||||
return containers, fmt.Errorf("Unknown format: %s", line)
|
||||
return containers, xerrors.Errorf("Unknown format: %s", line)
|
||||
}
|
||||
containers = append(containers, config.Container{
|
||||
ContainerID: fields[0],
|
||||
@@ -232,7 +235,7 @@ func (l *base) parseLxdPs(stdout string) (containers []config.Container, err err
|
||||
break
|
||||
}
|
||||
if len(fields) != 1 {
|
||||
return containers, fmt.Errorf("Unknown format: %s", line)
|
||||
return containers, xerrors.Errorf("Unknown format: %s", line)
|
||||
}
|
||||
containers = append(containers, config.Container{
|
||||
ContainerID: fields[0],
|
||||
@@ -265,7 +268,7 @@ func (l *base) ip() ([]string, []string, error) {
|
||||
// 2: eth0 inet6 fe80::5054:ff:fe2a:864c/64 scope link \ valid_lft forever preferred_lft forever
|
||||
r := l.exec("/sbin/ip -o addr", noSudo)
|
||||
if !r.isSuccess() {
|
||||
return nil, nil, fmt.Errorf("Failed to detect IP address: %v", r)
|
||||
return nil, nil, xerrors.Errorf("Failed to detect IP address: %v", r)
|
||||
}
|
||||
ipv4Addrs, ipv6Addrs := l.parseIP(r.Stdout)
|
||||
return ipv4Addrs, ipv6Addrs, nil
|
||||
@@ -358,7 +361,7 @@ func (l *base) detectRunningOnAws() (ok bool, instanceID string, err error) {
|
||||
return false, "", nil
|
||||
}
|
||||
}
|
||||
return false, "", fmt.Errorf(
|
||||
return false, "", xerrors.Errorf(
|
||||
"Failed to curl or wget to AWS instance metadata on %s. container: %s",
|
||||
l.ServerInfo.ServerName, l.ServerInfo.Container.Name)
|
||||
}
|
||||
@@ -388,22 +391,23 @@ func (l *base) convertToModel() models.ScanResult {
|
||||
}
|
||||
|
||||
return models.ScanResult{
|
||||
JSONVersion: models.JSONVersion,
|
||||
ServerName: l.ServerInfo.ServerName,
|
||||
ScannedAt: time.Now(),
|
||||
ScanMode: l.ServerInfo.Mode.String(),
|
||||
Family: l.Distro.Family,
|
||||
Release: l.Distro.Release,
|
||||
Container: container,
|
||||
Platform: l.Platform,
|
||||
IPv4Addrs: l.ServerInfo.IPv4Addrs,
|
||||
IPv6Addrs: l.ServerInfo.IPv6Addrs,
|
||||
ScannedCves: l.VulnInfos,
|
||||
RunningKernel: l.Kernel,
|
||||
Packages: l.Packages,
|
||||
SrcPackages: l.SrcPackages,
|
||||
Optional: l.ServerInfo.Optional,
|
||||
Errors: errs,
|
||||
JSONVersion: models.JSONVersion,
|
||||
ServerName: l.ServerInfo.ServerName,
|
||||
ScannedAt: time.Now(),
|
||||
ScanMode: l.ServerInfo.Mode.String(),
|
||||
Family: l.Distro.Family,
|
||||
Release: l.Distro.Release,
|
||||
Container: container,
|
||||
Platform: l.Platform,
|
||||
IPv4Addrs: l.ServerInfo.IPv4Addrs,
|
||||
IPv6Addrs: l.ServerInfo.IPv6Addrs,
|
||||
ScannedCves: l.VulnInfos,
|
||||
RunningKernel: l.Kernel,
|
||||
Packages: l.Packages,
|
||||
SrcPackages: l.SrcPackages,
|
||||
WordPressPackages: l.WordPress,
|
||||
Optional: l.ServerInfo.Optional,
|
||||
Errors: errs,
|
||||
}
|
||||
}
|
||||
|
||||
@@ -427,7 +431,7 @@ func (l *base) detectInitSystem() (string, error) {
|
||||
f = func(cmd string) (string, error) {
|
||||
r := l.exec(cmd, sudo)
|
||||
if !r.isSuccess() {
|
||||
return "", fmt.Errorf("Failed to stat %s: %s", cmd, r)
|
||||
return "", xerrors.Errorf("Failed to stat %s: %s", cmd, r)
|
||||
}
|
||||
scanner := bufio.NewScanner(strings.NewReader(r.Stdout))
|
||||
scanner.Scan()
|
||||
@@ -449,7 +453,7 @@ func (l *base) detectInitSystem() (string, error) {
|
||||
}
|
||||
return sysVinit, nil
|
||||
}
|
||||
return "", fmt.Errorf("Failed to detect a init system: %s", line)
|
||||
return "", xerrors.Errorf("Failed to detect a init system: %s", line)
|
||||
}
|
||||
return f("stat /proc/1/exe")
|
||||
}
|
||||
@@ -458,7 +462,7 @@ func (l *base) detectServiceName(pid string) (string, error) {
|
||||
cmd := fmt.Sprintf("systemctl status --quiet --no-pager %s", pid)
|
||||
r := l.exec(cmd, noSudo)
|
||||
if !r.isSuccess() {
|
||||
return "", fmt.Errorf("Failed to stat %s: %s", cmd, r)
|
||||
return "", xerrors.Errorf("Failed to stat %s: %s", cmd, r)
|
||||
}
|
||||
return l.parseSystemctlStatus(r.Stdout), nil
|
||||
}
|
||||
@@ -473,3 +477,125 @@ func (l *base) parseSystemctlStatus(stdout string) string {
|
||||
}
|
||||
return ss[1]
|
||||
}
|
||||
|
||||
func (l *base) scanWordPress() (err error) {
|
||||
wpOpts := []string{l.ServerInfo.WordPress.OSUser,
|
||||
l.ServerInfo.WordPress.DocRoot,
|
||||
l.ServerInfo.WordPress.CmdPath,
|
||||
l.ServerInfo.WordPress.WPVulnDBToken,
|
||||
}
|
||||
var isScanWp, hasEmptyOpt bool
|
||||
for _, opt := range wpOpts {
|
||||
if opt != "" {
|
||||
isScanWp = true
|
||||
break
|
||||
} else {
|
||||
hasEmptyOpt = true
|
||||
}
|
||||
}
|
||||
if !isScanWp {
|
||||
return nil
|
||||
}
|
||||
|
||||
if hasEmptyOpt {
|
||||
return xerrors.Errorf("%s has empty WordPress opts: %s",
|
||||
l.getServerInfo().GetServerName(), wpOpts)
|
||||
}
|
||||
|
||||
cmd := fmt.Sprintf("sudo -u %s -i -- %s cli version",
|
||||
l.ServerInfo.WordPress.OSUser,
|
||||
l.ServerInfo.WordPress.CmdPath)
|
||||
if r := exec(l.ServerInfo, cmd, noSudo); !r.isSuccess() {
|
||||
l.ServerInfo.WordPress.WPVulnDBToken = "secret"
|
||||
return xerrors.Errorf("Failed to exec `%s`. Check the OS user, command path of wp-cli, DocRoot and permission: %#v", cmd, l.ServerInfo.WordPress)
|
||||
}
|
||||
|
||||
wp, err := l.detectWordPress()
|
||||
if err != nil {
|
||||
return xerrors.Errorf("Failed to scan wordpress: %w", err)
|
||||
}
|
||||
l.WordPress = wp
|
||||
return nil
|
||||
}
|
||||
|
||||
func (l *base) detectWordPress() (*models.WordPressPackages, error) {
|
||||
ver, err := l.detectWpCore()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
themes, err := l.detectWpThemes()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
plugins, err := l.detectWpPlugins()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
pkgs := models.WordPressPackages{
|
||||
models.WpPackage{
|
||||
Name: models.WPCore,
|
||||
Version: ver,
|
||||
Type: models.WPCore,
|
||||
},
|
||||
}
|
||||
pkgs = append(pkgs, themes...)
|
||||
pkgs = append(pkgs, plugins...)
|
||||
return &pkgs, nil
|
||||
}
|
||||
|
||||
func (l *base) detectWpCore() (string, error) {
|
||||
cmd := fmt.Sprintf("sudo -u %s -i -- %s core version --path=%s",
|
||||
l.ServerInfo.WordPress.OSUser,
|
||||
l.ServerInfo.WordPress.CmdPath,
|
||||
l.ServerInfo.WordPress.DocRoot)
|
||||
|
||||
r := exec(l.ServerInfo, cmd, noSudo)
|
||||
if !r.isSuccess() {
|
||||
return "", xerrors.Errorf("Failed to get wp core version: %s", r)
|
||||
}
|
||||
return strings.TrimSpace(r.Stdout), nil
|
||||
}
|
||||
|
||||
func (l *base) detectWpThemes() ([]models.WpPackage, error) {
|
||||
cmd := fmt.Sprintf("sudo -u %s -i -- %s theme list --path=%s --format=json",
|
||||
l.ServerInfo.WordPress.OSUser,
|
||||
l.ServerInfo.WordPress.CmdPath,
|
||||
l.ServerInfo.WordPress.DocRoot)
|
||||
|
||||
var themes []models.WpPackage
|
||||
r := exec(l.ServerInfo, cmd, noSudo)
|
||||
if !r.isSuccess() {
|
||||
return nil, xerrors.Errorf("Failed to get a list of WordPress plugins: %s", r)
|
||||
}
|
||||
err := json.Unmarshal([]byte(r.Stdout), &themes)
|
||||
if err != nil {
|
||||
return nil, xerrors.Errorf("Failed to unmarshal wp theme list: %w", cmd, err)
|
||||
}
|
||||
for i := range themes {
|
||||
themes[i].Type = models.WPTheme
|
||||
}
|
||||
return themes, nil
|
||||
}
|
||||
|
||||
func (l *base) detectWpPlugins() ([]models.WpPackage, error) {
|
||||
cmd := fmt.Sprintf("sudo -u %s -i -- %s plugin list --path=%s --format=json",
|
||||
l.ServerInfo.WordPress.OSUser,
|
||||
l.ServerInfo.WordPress.CmdPath,
|
||||
l.ServerInfo.WordPress.DocRoot)
|
||||
|
||||
var plugins []models.WpPackage
|
||||
r := exec(l.ServerInfo, cmd, noSudo)
|
||||
if !r.isSuccess() {
|
||||
return nil, xerrors.Errorf("Failed to wp plugin list: %s", r)
|
||||
}
|
||||
if err := json.Unmarshal([]byte(r.Stdout), &plugins); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
for i := range plugins {
|
||||
plugins[i].Type = models.WPPlugin
|
||||
}
|
||||
return plugins, nil
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user