feat(scan): Display listen port of affected procs for each vulnerable pkgs (#859)

* refactor(redhat): move rpmQa and rpmQf to redhatbase.go

* feat(scan): Display listen port of affected procs
This commit is contained in:
Kota Kanbe
2019-07-03 23:01:59 +09:00
committed by GitHub
parent 3e5390309c
commit 836e4704f8
12 changed files with 141 additions and 46 deletions

View File

@@ -25,7 +25,7 @@ GO_OFF := GO111MODULE=off go
all: build
build: main.go pretest
build: main.go pretest fmt
$(GO) build -a -ldflags "$(LDFLAGS)" -o vuls $<
b: main.go pretest

View File

@@ -185,8 +185,9 @@ type Changelog struct {
// AffectedProcess keep a processes information affected by software update
type AffectedProcess struct {
PID string `json:"pid"`
Name string `json:"name"`
PID string `json:"pid,omitempty"`
Name string `json:"name,omitempty"`
ListenPorts []string `json:"listenPorts,omitempty"`
}
// NeedRestartProcess keep a processes information affected by software update

View File

@@ -733,7 +733,8 @@ func setChangelogLayout(g *gocui.Gui) error {
if len(pack.AffectedProcs) != 0 {
for _, p := range pack.AffectedProcs {
lines = append(lines, fmt.Sprintf(" * PID: %s %s", p.PID, p.Name))
lines = append(lines, fmt.Sprintf(" * PID: %s %s Port: %s",
p.PID, p.Name, p.ListenPorts))
}
} else {
// lines = append(lines, fmt.Sprintf(" * No affected process"))

View File

@@ -262,7 +262,7 @@ No CVE-IDs are found in updatable packages.
if len(pack.AffectedProcs) != 0 {
for _, p := range pack.AffectedProcs {
data = append(data, []string{"",
fmt.Sprintf(" - PID: %s %s", p.PID, p.Name)})
fmt.Sprintf(" - PID: %s %s, Port: %s", p.PID, p.Name, p.ListenPorts)})
}
}
}

View File

@@ -85,6 +85,7 @@ func (o *amazon) sudoNoPasswdCmdsFastRoot() []cmd {
{"stat /proc/1/exe", exitStatusZero},
{"ls -l /proc/1/exe", exitStatusZero},
{"cat /proc/1/maps", exitStatusZero},
{"lsof -i -P", exitStatusZero},
}
}

View File

@@ -781,3 +781,30 @@ func (l *base) parseGrepProcMap(stdout string) (soPaths []string) {
}
return soPaths
}
func (l *base) lsOfListen() (stdout string, err error) {
cmd := `lsof -i -P | grep LISTEN`
r := l.exec(util.PrependProxyEnv(cmd), sudo)
if !r.isSuccess() {
return "", xerrors.Errorf("Failed to SSH: %s", r)
}
return r.Stdout, nil
}
func (l *base) parseLsOf(stdout string) map[string]string {
portPid := map[string]string{}
scanner := bufio.NewScanner(strings.NewReader(stdout))
for scanner.Scan() {
ss := strings.Fields(scanner.Text())
if len(ss) < 10 {
continue
}
pid, ipPort := ss[1], ss[8]
sss := strings.Split(ipPort, ":")
if len(sss) != 2 {
continue
}
portPid[sss[1]] = pid
}
return portPid
}

View File

@@ -252,3 +252,43 @@ func Test_base_parseGrepProcMap(t *testing.T) {
})
}
}
func Test_base_parseLsOf(t *testing.T) {
type args struct {
stdout string
}
tests := []struct {
name string
args args
wantPortPid map[string]string
}{
{
name: "lsof",
args: args{
stdout: `systemd-r 474 systemd-resolve 13u IPv4 11904 0t0 TCP localhost:53 (LISTEN)
sshd 644 root 3u IPv4 16714 0t0 TCP *:22 (LISTEN)
sshd 644 root 4u IPv6 16716 0t0 TCP *:22 (LISTEN)
squid 959 proxy 11u IPv6 16351 0t0 TCP *:3128 (LISTEN)
node 1498 ubuntu 21u IPv6 20132 0t0 TCP *:35401 (LISTEN)
node 1498 ubuntu 22u IPv6 20133 0t0 TCP *:44801 (LISTEN)
docker-pr 9135 root 4u IPv6 297133 0t0 TCP *:6379 (LISTEN)`,
},
wantPortPid: map[string]string{
"53": "474",
"22": "644",
"3128": "959",
"35401": "1498",
"44801": "1498",
"6379": "9135",
},
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
l := &base{}
if gotPortPid := l.parseLsOf(tt.args.stdout); !reflect.DeepEqual(gotPortPid, tt.wantPortPid) {
t.Errorf("base.parseLsOf() = %v, want %v", gotPortPid, tt.wantPortPid)
}
})
}
}

View File

@@ -96,6 +96,7 @@ func (o *centos) sudoNoPasswdCmdsFastRoot() []cmd {
{"stat /proc/1/exe", exitStatusZero},
{"ls -l /proc/1/exe", exitStatusZero},
{"cat /proc/1/maps", exitStatusZero},
{"lsof -i -P", exitStatusZero},
}
}
return []cmd{

View File

@@ -151,6 +151,7 @@ func (o *debian) checkIfSudoNoPasswd() error {
"stat /proc/1/exe",
"ls -l /proc/1/exe",
"cat /proc/1/maps",
"lsof -i -P",
}
if !o.getServerInfo().Mode.IsOffline() {
@@ -1152,6 +1153,16 @@ func (o *debian) dpkgPs() error {
pidLoadedFiles[pid] = append(pidLoadedFiles[pid], ss...)
}
pidListenPorts := map[string][]string{}
stdout, err = o.lsOfListen()
if err != nil {
return xerrors.Errorf("Failed to ls of: %w", err)
}
portPid := o.parseLsOf(stdout)
for port, pid := range portPid {
pidListenPorts[pid] = append(pidListenPorts[pid], port)
}
for pid, loadedFiles := range pidLoadedFiles {
o.log.Debugf("dpkg -S %#v", loadedFiles)
pkgNames, err := o.getPkgName(loadedFiles)
@@ -1165,8 +1176,9 @@ func (o *debian) dpkgPs() error {
procName = pidNames[pid]
}
proc := models.AffectedProcess{
PID: pid,
Name: procName,
PID: pid,
Name: procName,
ListenPorts: pidListenPorts[pid],
}
for _, n := range pkgNames {

View File

@@ -278,7 +278,7 @@ func (o *redhatBase) scanInstalledPackages() (models.Packages, error) {
Version: version,
}
r := o.exec(rpmQa(o.Distro), noSudo)
r := o.exec(o.rpmQa(o.Distro), noSudo)
if !r.isSuccess() {
return nil, xerrors.Errorf("Scan packages failed: %s", r)
}
@@ -482,9 +482,9 @@ func (o *redhatBase) yumPs() error {
if err != nil {
return xerrors.Errorf("Failed to yum ps: %w", err)
}
pidNames := o.parsePs(stdout)
pidLoadedFiles := map[string][]string{}
// for pid, name := range pidNames {
for pid := range pidNames {
stdout := ""
stdout, err = o.lsProcExe(pid)
@@ -508,6 +508,16 @@ func (o *redhatBase) yumPs() error {
pidLoadedFiles[pid] = append(pidLoadedFiles[pid], ss...)
}
pidListenPorts := map[string][]string{}
stdout, err = o.lsOfListen()
if err != nil {
return xerrors.Errorf("Failed to ls of: %w", err)
}
portPid := o.parseLsOf(stdout)
for port, pid := range portPid {
pidListenPorts[pid] = append(pidListenPorts[pid], port)
}
for pid, loadedFiles := range pidLoadedFiles {
o.log.Debugf("rpm -qf: %#v", loadedFiles)
pkgNames, err := o.getPkgName(loadedFiles)
@@ -526,8 +536,9 @@ func (o *redhatBase) yumPs() error {
procName = pidNames[pid]
}
proc := models.AffectedProcess{
PID: pid,
Name: procName,
PID: pid,
Name: procName,
ListenPorts: pidListenPorts[pid],
}
for fqpn := range uniq {
@@ -634,7 +645,7 @@ func (o *redhatBase) procPathToFQPN(execCommand string) (string, error) {
}
func (o *redhatBase) getPkgName(paths []string) (pkgNames []string, err error) {
cmd := rpmQf(o.Distro) + strings.Join(paths, " ")
cmd := o.rpmQf(o.Distro) + strings.Join(paths, " ")
r := o.exec(util.PrependProxyEnv(cmd), noSudo)
if !r.isSuccess() {
return nil, xerrors.Errorf("Failed to SSH: %s", r)
@@ -652,3 +663,37 @@ func (o *redhatBase) getPkgName(paths []string) (pkgNames []string, err error) {
}
return pkgNames, nil
}
func (o *redhatBase) rpmQa(distro config.Distro) string {
const old = `rpm -qa --queryformat "%{NAME} %{EPOCH} %{VERSION} %{RELEASE} %{ARCH}\n"`
const new = `rpm -qa --queryformat "%{NAME} %{EPOCHNUM} %{VERSION} %{RELEASE} %{ARCH}\n"`
switch distro.Family {
case config.SUSEEnterpriseServer:
if v, _ := distro.MajorVersion(); v < 12 {
return old
}
return new
default:
if v, _ := distro.MajorVersion(); v < 6 {
return old
}
return new
}
}
func (o *redhatBase) rpmQf(distro config.Distro) string {
const old = `rpm -qf --queryformat "%{NAME} %{EPOCH} %{VERSION} %{RELEASE} %{ARCH}\n" `
const new = `rpm -qf --queryformat "%{NAME} %{EPOCHNUM} %{VERSION} %{RELEASE} %{ARCH}\n" `
switch distro.Family {
case config.SUSEEnterpriseServer:
if v, _ := distro.MajorVersion(); v < 12 {
return old
}
return new
default:
if v, _ := distro.MajorVersion(); v < 6 {
return old
}
return new
}
}

View File

@@ -89,6 +89,7 @@ func (o *rhel) sudoNoPasswdCmdsFastRoot() []cmd {
{"stat /proc/1/exe", exitStatusZero},
{"ls -l /proc/1/exe", exitStatusZero},
{"cat /proc/1/maps", exitStatusZero},
{"lsof -i -P", exitStatusZero},
}
}
return []cmd{

View File

@@ -50,37 +50,3 @@ func isRunningKernel(pack models.Package, family string, kernel models.Kernel) (
}
return false, false
}
func rpmQa(distro config.Distro) string {
const old = `rpm -qa --queryformat "%{NAME} %{EPOCH} %{VERSION} %{RELEASE} %{ARCH}\n"`
const new = `rpm -qa --queryformat "%{NAME} %{EPOCHNUM} %{VERSION} %{RELEASE} %{ARCH}\n"`
switch distro.Family {
case config.SUSEEnterpriseServer:
if v, _ := distro.MajorVersion(); v < 12 {
return old
}
return new
default:
if v, _ := distro.MajorVersion(); v < 6 {
return old
}
return new
}
}
func rpmQf(distro config.Distro) string {
const old = `rpm -qf --queryformat "%{NAME} %{EPOCH} %{VERSION} %{RELEASE} %{ARCH}\n" `
const new = `rpm -qf --queryformat "%{NAME} %{EPOCHNUM} %{VERSION} %{RELEASE} %{ARCH}\n" `
switch distro.Family {
case config.SUSEEnterpriseServer:
if v, _ := distro.MajorVersion(); v < 12 {
return old
}
return new
default:
if v, _ := distro.MajorVersion(); v < 6 {
return old
}
return new
}
}