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:
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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"))
|
||||
|
||||
@@ -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)})
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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},
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
27
scan/base.go
27
scan/base.go
@@ -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
|
||||
}
|
||||
|
||||
@@ -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)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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{
|
||||
|
||||
@@ -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 {
|
||||
|
||||
@@ -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
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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{
|
||||
|
||||
@@ -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
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user